单元测试是白盒测试,又被称为模块测试,是最小的测试单元。
单测应该做到:
- 一次检测一个方法
- 提供方法所需的参数
- 验证结果的准确性
Why
为什么需要单测
- 正确性保证
- 提高代码质量,高内聚,低耦合
- 代码重构时回归验证
测试内容
测试的内容可以包括
- 模块接口
- 局部数据结构
- 分支路径
- 错误处理
- 边界测试
评价测试的指标,覆盖范围,是否测试代码覆盖了基本表达语句,基本逻辑块。
- 语句覆盖,每一个基本语句是否被覆盖
- 判定覆盖,分支的每一个路径是否都被覆盖
- 循环覆盖,循环体,0,1,多次是否都被覆盖
Mock 工具
Mock 使用场景
- 依赖外部调用
- DAO 层,数据库等底层存储调用
- 系统间异步通知
- 应用中类(Abstract,final,static),接口等
Mock 工具工作的过程
- Record 过程,准备数据阶段,创建依赖的 Class,Interface 后者 Method,模拟返回的数据,耗时,调用次数等等
- Replay 阶段,调用被测试代码,执行测试,期间 Invoke 上一阶段准备的 Mock 对象或者方法
- Verify 阶段,验证调用是否正确,Mock 方法调用次数,顺序等等
目前比较流行的 Mock 工具有 EasyMock,jMock,Mockito,Unitils,Mock,PowerMock,JMockit 等等
Best practices
Unit test 执行前后不能对环境造成污染,避免写有副作用的 TestCase
自我清理:
- 文件
- H2 内存数据库
- MySQL 事务回滚
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
保持效率
- Unit test 中不要 Sleep
- 对于异步操作,用 Future 等待结果或者使用 CountDownLatch 通知
测试粒度
- 测试尽量小,执行速度快
- 一次测试一个对象,出现问题容易排查
- 测试方法应当尽量简单明了
结果验证
- 尽量使用 Unit test 提供的 assert/fail 方法
保持独立
- 执行顺序不确定,因此测试用例之间一定要保持完全独立,不能相互依赖。
仅测试公有接口
- 测试类公有 API
- 一些测试工具允许测试类私有成员,应该尽量避免测试私有成员,否则会让测试变得繁琐难以维护
- 如果私有成员需要进行直接测试,可以考虑重构到工具类公有方法中
覆盖边界值
– 确保参数边界值均被覆盖。对于数字,测试负数、 0 、正数、最小值、最大值、 NaN (非数字)、无穷大等 – 对于字符串,测试空字符串、单字符、非 ASCII 字符串、多字节字符串等 – 对于集合类型,测试空、 1 、第一个、最后一个等 – 对于日期,测试 1 月 1 号、 2 月 29 号、 12 月 31 号等
编写反向测试
- 刻意编写问题代码,验证鲁棒性和是否能够正确处理
- 异常处理方法
单元测试无法证明代码正确性,一个失败的单测表明代码可能有错误,但一个成功的单测什么也不能证明。