单元测试

脏测试等同于甚至坏于没测试

测试必须随着生产代码演进而修改

单元测试使代码可扩展 可维护 可复用

测试类型

重要性

原则

AIR

FIRST

粒度与范围

单元测试与集成测试

集成测试运行通常更慢,很难编写,很难做到自动化,需要配置,通常一次测试的东西过多,并且集成测试会使用真实的依赖,而单元测试则把被测试的单元和其依赖隔离,以保证单元测试的高度稳定,还可以轻易控制和模拟被测试单元的行为方面

方法

传统方法

TDD测试驱动开发

TDD看起来挺美好,但在现阶段国内的唯快不破商业环境下,代码迭代频繁,使用起来还是有难度的

单元测试的复杂性

  1. 输入参数的复杂性:输入参数不是简单的函数输入参数,本质上讲,任何能够影响代码执行路径的参数,都是被测函数的输入参数
  2. 预期输出的复杂性:主要表现在预期输出应该包括被测函数执行完成后所改写的所有数据
  3. 关联依赖:需要采用桩代码来模拟不可用的代码,并通过打桩补齐未定义部分

单元测试代码规范

整洁的测试代码

构造-操作-检验

通过不断重构测试就会慢慢得到一个文档的测试API 随着重构代码也会更有表现力

assertUserExistsInDatabase("cxk"); // 一个测试API

对于测试环境 CPU内存等资源没有那么紧张 所以测试也并非一定要追求效率

每个测试应拥有尽可能少的断言 个人认为 可以对断言进行抽象 如上述的测试API

同时 测试应只测试一个概念 将多件事混杂在一起只会导致概念的混乱

@Test
void testUserService(){...} // bad 过于混杂
@Test
void testUserInsertFailed(){...} // better

命名:不要将太多描述放到测试函数命名中,应该放到函数的注释中

断言数量最小化

不要迷信过高覆盖率

单元测试的结构

可测的代码

不可测的原因

  1. 依赖了外部接口组件
  2. 外部接口组件没有返回期望的返回数据
  3. 执行外部接口组件会产生副作用

可测改造

对于外部接口组件,使用依赖注入或者通过外部传参的方式来使用,这样利于在测试时进行接缝

  1. 对象接缝:通过继承 DOC 来改变默认行为
  2. 接口接缝:将 DOC 提取为接口,并用其他实现类来改变默认行为
  3. 新生:对于老代码,使用一个新添加的方法来完成新需求,使得新代码可测
  4. 包装:老代码不动,新代码通过包装调用老代码并添加额外职责的方式完成需求

单元测试覆盖率

粗粒度覆盖

细粒度覆盖

行覆盖

执行的语句总数 / 全部语句总数

分支覆盖

if (condition) {...} // condition 为真为假的时候都要测试

条件判定覆盖

if (condition1 && condition2) {...} // condtion1 和 condtion2 为真为假的组合 也就是要4种组合

条件组合覆盖

路径覆盖

覆盖率工具

构造数据

java-faker

能生成具体有意义的字符串

easy-random

easy-random 可以轻松构造复杂对象,支持定义对象中集合长度,字符串长度范围,生成集合等

对哪些代码写单测

一般要设置自动回滚。除此之外,还可以整合H2等内存数据库来对数据访问层代码进行测试

一般要依赖 mock 工具,将服务的所有依赖都 mock 掉

因为工具类一般在服务内共用,如果有 BUG,影响面很大,很容易造成线上问题或故障。一般需要构造正常和边界值两种类型的用例,对工具类进行全面的测试,才可放心使用