单元测试介绍
什么是单元测试
单元测试是指对软件中的最小可测试单元进行检查和验证。相信大多数都看见过这幅测试模型图,根据测试金字塔原理,越往上层的测试,所需的测试投入比例越大,效果也越差,而单元测试的成本要小的多,也更容易发现问题。话虽然这么说,但是写单元测试对于大部分开发来说依然是一件很蛋疼的事。在以前没有QA的年代,TDD单元测试驱动开发以前还是很火的,现在就不知道了。因为那个时候没有QA,开发都得靠自己测试,在重构项目时,通过单元测试可以快速的了解到自己重构时发生的修改,然后重新修正。但是现在有专业的QA同学,他们会写集成测试,导致了开发更加不想写单元测试了。但是很多公司都会有单元测试的硬性要求加上谁知道QA是不是一个和蔼可亲的小姐姐,所以单元测试还是得写,不管用不用得上,万一用得上呢?
单元测试和集成测试
单元测试和集成测试的界线我相信大部分开发也是不清晰的。我也不是特别清楚。个人理解单元测试针对于一块业务逻辑最小的单元,可以简单理解为一个类的方法。一个单元测试不应该包含外部依赖的逻辑,反之就是集成测试了。 但是一个service的一个接口实现一般都会依赖很多第三方:1.本地其它的service 2.dao调用 3.rpc调用 4.微服务调用,等等。相信大家一定身有感受。
但是我们遇到的就是这样的情况,那我们还怎么写单元测试呀?答案是MOCK。现在有很多Mock的开源框架比如:mockito,easymock,还有更强大的powermock,最后放了使用参考链接。那么问题来了,既然我们可以mock第三方远程依赖,为何不mock dao、local service呢?没错外部依赖全部mock掉,就是单元测试了。因为我们只关心所测试的方法的业务逻辑,也就是真正高内聚的逻辑单元了。
有了Mock,我们发现:没有什么数据是造不出来的,而且还不产生任何脏数据, 跑case更快了,因为不用启动整个项目。是不是整个世界都清晰了,但是一想又有点害怕,毕竟都mock了还测试个鸡儿。还是回到对单元测试得理解,单元测试应该只针对于目标方法的业务逻辑测试,dao、其它service应该在它们自身的单元测试去测试。对于依赖的第三方,我们应该信任它们能正确的完成我们所预期的。我们应该验证的内容是:方法是否被调用,调用的次数对不对,调用参数对不对,只要这三个验证通过,就OK了。Mockito框架的verify接口就是做这件事情的。如果你理解了上述内容,那么你就开窍了,UT不在变得这么难写。
单元测试规范
好的单元测试必须遵守AIR 原则
A:Automatic(自动化)
I: Independent(独立性)
R: Repeatable(可重复)
- 单元测试必须是全自动的,非交互式的,必须使用assert 验证,不能使用 System.out.println 肉眼观察执行结果,无参方法测试后,需要使用verify 进行验证
- 保持单元测试的独立性。单元测试用例之间不能相互调用,不能依赖执行的先后顺序
单元测试是可以重复执行的,不能受到外界环境的影响
- 业务中使用了localcache、redis 缓存的,执行测试用例前,先清理缓存
- DB 的测试,不可以依赖真实的DB,需要用 H2 测试框架mock 数据层,使用方式:http://www.h2database.com/html/main.html
- 所有的第三方 dubbo 调用,也需要mock 调,不可以真实调用。
- 所有的中间件mock 掉。
单元测试必须写在如下工程目录中: src/test/java,不允许写在业务代码目录下。禁止通过在业务代码类中写 main 函数进行测试
- 单元测试的BCDE 原则,保证被测模块的交付质量
- B: Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
- C:Correct,正确的输入,并得到预期结果。
- D: Design,与设计文档结合,编写测试用例
- E: Error,错误的信息输入,得到预期结果
单元测试实践
单元测试的过程
单元测试的编写,主要包含以下几个阶段:
- 数据准备:在编写测试用例前,尤其是Dao层,需要依赖到一些数据,数据来源一般是数据库,而构造数据,又不能依赖 DAO 层的代码,需要使用原生jdbc 去插入数据,测试代码编写效率低。
- 构造参数及打桩(stub):调用方法需要传递入参,有时候一个入参十几个参数需要 set,set 方法写完,代码已经写了十来行了。
- 执行测试:这一步比较简单,直接调用被测方法即可。
- 结果验证:这里除了验证被测方法的返回值外,还需要验证插入到数据库中的 数据是否正确,某外部方法被调用过n次或未调用过。
- 必要的清理:对打桩进行清理,对数据库脏数据进行清理。
最佳实践
基于上面的问题,我们可以采用如下的解决方案:
spring-test+powermock +h2数据库 的测试框架
- H2数据库
H2数据库非常适合在测试程序中使用,程序关闭时自动清理数据,H2 数据库的表结构初始化是通过 jdbc:initialize-database 标签实现的,单元测试中使用 H2 数据库非常简单,仅需修改 jdbc 连接即可。 - powermock
powermock支持静态方法 mock,同时兼容 mockito,powermock 示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14(PowerMockRunner.class)
public class XXXXTest {
private YourTestService yourTestService;
private YourRPCClient yourRPCClient;
private void test_method() {
// 构造参数,这里可以使用json构造
}