什么是单元测试
概念
-
单元测试是研发工程师编写,用来测试自己写的代码的正确性。
-
几乎总是使用单元测试框架编写。
特性
-
一般是一个函数配几个单元测试
-
单元测试不应该依赖外部系统
-
单元测试运行速度很快
-
单元测试不应该造成测试环境的脏数据
-
单元测试可以重复运行
单元测试VS集成测试
-
测试粒度不同,单元测试的粒度更小。
-
集成测试的测试对象是整个系统或者是某个功能模块的功能是否正常,比如用户登录注册、字典管理模块等,是一种端到端的测试。
-
单元测试的粒度至多是类级别,通常是方法级别的,是代码层级的测试。只有测试粒度小才能在出错时尽快定位到出错位置
为什么要写单元测试
-
单元测试是集成测试有力的补充
-
写单元测试,其实是为对象模型添加了一个特殊用户,这个过程也迫使我们把对象模型设计的更加易用
-
写单元测试的过程本身也是代码重构的过程,因为必要的时候需要进行重构才能继续编写测试(后面举例说明)
-
单元测试可以加深对高内聚、低耦合、面向接口编程、依赖注入、API 设计、单一职责等编程思想的掌握
-
阅读单元测试能够快速熟悉代码。单元测试实际上就是用户用例,反应了代码的功能和如何使用。是注释和文档的有力补充。
-
单元测试是TDD(Test-Driven Development)可落地执行的改进方案
好的单元测试准则AIR原则(阿里单元测试准则)
如何编写单元测试-经验之谈
写单元测试真的是件很耗时的事情吗?
单元测试的代码量可能是被测试代码的1-2倍,写的过程也很繁琐,考虑大量的测试用例。但其实也没有那么耗时,毕竟测试用例不需要考虑代码设计上的问题。正常估计时间0.3-0.5之间。
对单元测试的代码质量有什么要求吗?
单元测试的代码不会线上运行,每个类的测试代码也比较独立,基本不相互依赖。所以相对于被测试代码,单元测试代码的质量可以放低要求,命名不规范,代码重复也都没有问题。
单元测试只要覆盖率高就可以了吗?
单元测试覆盖率是比较容易量化的指标,常常作为单元测试写得好坏的评判标准。有很多现成的工具专门用来做覆盖率统计,比如,JaCoCo、Cobertura、Emma、Clover。覆盖率的计算方式有很多种,比较简单的是语句覆盖,稍微高级点的有:条件覆盖、判定覆盖、路径覆盖。
不管覆盖率的计算方式如何高级,将覆盖率作为衡量单元测试质量的唯一标准是不合理的。实际上更重要的是要看测试用例是否覆盖了所有可能的情况。简单举例如下:
public double cal(double a, double b) { return a / b; }
我们只需要一个测试用例就可以做到100%覆盖率,比如cal(10.0, 2.0),但并不代表测试足够全面了,我们还需要考虑,当除数等于0的情况下,代码执行是否符合预期。一个项目的单元测试覆盖率在60~70%即可上线。如果项目对代码质量要求比较高,可以适当提高单元测试覆盖率的要求
写单元测试需要了解代码的实现逻辑吗?
单元测试不需要关心被测试函数的具体逻辑,只关心被测试函数实现了什么功能。因为一旦对代码进行了重构,代码的外部行为不变的情况下,对代码的实现逻辑进行了修改,那原本的单元测试就会运行失败。
如何选择单元测试框架?
写单元测试本身不需要太复杂的技术,大部分框架都可以满足需求。只要公司内部统一就行。
单元测试框架 junit,mock框架 Mockito
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
DAO层测试
DAO层的测试有些不太一样,不能再使用Mock,否则无法验证SQL是否正确。
对于DAO测试有一般最简的方式是直接使用@SpringBootTest
注解启动测试环境,通过Spring创建Mybatis、Mapper实例,但这种方式并不属于单元测试,而是集成测试范畴了,因为当启用@SpringBootTest
时,会把整个应用的上下文加载进来。不仅耗时时间长,而且一旦依赖环境上有任何问题,可能会影响启动,进而影响DAO层的测试。
最后,需要到数据库尽可能隔离,因为如果大家都使用同一个Test环境的数据的话,一旦测试用例编写有问题,就可能会污染Test环境的数据。
针对以上场景,可采用以下方案: 1. 通过MyBatis的SqlSession启动mapper实例(避免通过Spring启动加载上下文信息)。 2. 通过内存数据库(如H2)隔离大家的数据库连接(完全隔离不会存在互相干扰的现象)。 3. 通过DBUnit工具,用作对于数据库层的操作访问工具。
总结
单元测试的三个步骤
-
准备数据、行为
-
测试目标模块
-
验证测试结果
如何写
分层单测:数据库操作层、中间件依赖层、业务逻辑层,各自的单元测试各自写,互相不要有依赖
-
dao层测试,使用H2进行测试,做独立的BaseH2Test、独立的test-h2-applicationContext.xml,只对dao的测试
-
service层测试,依赖mockito框架
-
对于依赖外部的中间件(例如redis、diamond、mq),在处理单测的时候要注意分开加载和测试,尤其是与dao的测试分开