Unit Test 覆盖率

Unit Test

1.写单元测试的收益

  • 单元测试能帮助每个人深入代码细节,了解代码的功能。
  • 通过测试用例我们能发现 bug,并提交代码的健壮性。
  • 测试用例同时也是代码的 demo 用法。

2.单元测试用例的一些设计原则

  • 应该精心设计好步骤,颗粒度和组合条件。
  • 注意边界条件。
  • 单元测试也应该好好设计,不要写无用的代码。
  • 当你发现一个方法很难写单元测试时,如果可以确认这个方法臭代码,那么就和开发者一起重构它。
  • TDD(可选):当你开始写一个新的功能时,你可以试着先写测试用例。

3.测试覆盖率设定值

单元测试基本准则

1: 隔离性与单一性

一个测试用例应该精确到方法级别,并应该能够单独执行该测试用例。同时关注点也始终在该方法上(只测试该方法)。

如果方法过于复杂,开发阶段就应该将其再次进行拆分,对于测试用例来讲,最佳做法是一个用例只关注一个分支(判断)。当对其进行修改后,也仅仅影响一个测试用例的成功与否。这会极大方便我们在开发阶段验证问题和解决问题,但与此同时,也对我们覆盖率提出了极大的挑战。

2:自动性

单元测试能够自动化进行。强制要求:所有的单元测试必须写在 src/test 下,同时方法命名应该符合规范。基准测试除外。

3:可重复性

多次执行(任何环境任何时间)结果唯一,且可以重复执行。

4:轻量型

即任何环境都可快速执行。

这要求我们尽可能不要依赖太多组件,如各种 spring bean 之类的。在单元测试中,这些都是可被 mock 的,增加这些,会加大我们单测的执行速度,同时也可能会传递污染。

对于一些数据库、其他外部组件等。尽可能也采用模拟客户端的形式,即不依赖于外部环境,(任何外部依赖的存在都会极大的限制测试用例的可迁移性和稳定性以及结果正确性),这同时也方便开发者在任何环境都能够进行测试。

5: 可测性

这么多年过去了,你所看到的 mockito 已经成长为 mock 界的 NO.1 了,但他依然不支持 mock 静态方法、构造方法等。甚至官网上一直写着: “Don’t mock everything” 。因此尽量少用静态方法。

一般建议只在一些工具类提供静态方法,这种情况下也不需要 mock,直接使用真实类即可。如果被依赖类不是工具类,可以将静态方法重构为实例方法。这样更加符合面向对象的设计理念。

6: 完备性

测试覆盖率,这是个非常费劲的问题,对于核心流程,我们是希望能够达到 90% 的覆盖率,非核心流程要求 60% 以上。

覆盖率足够高的情况下会减少足够多的 bug 出现的概率,同时也减少了我们回归测试的成本。这是一个长久的工作,每当开发者新增或者修改代码的时候,相关测试用例与此同时也需要完善。这一点,希望开发者以及相关代码 reviewer 都能足够重视。

7:拒绝无效断言

无效断言让测试本身变得毫无意义,它和你的代码正确与否几乎没什么关系,且有可能会给你造成一种成功的假象,这种假象有可能持续到你的代码部署到生产环境。

关于无效的断言这么几种类型

1:不同类型的比较。

2:判断一个具有默认值的对象或者变量不为空。

这本身显得毫无意义,因此,在进行相关判断的时候应该关注一下其本身是否含有默认值。

3:断言尽可能采用肯定断言而非否定断言,断言尽可能在一个预知结果范围内,或者是准确的数值,(否则有可能会导致一些不符合你的实际预期但是通过了断言)除非你的代码只关心他是否为空。

8:一些单测的注意点

1:Thread.sleep()

测试代码中尽量不要使用 Thread.sleep,这让测试变得不稳定,可能会因为环境或者负载而意外导致失败。建议采用以下方式:

Awaitility.await().atMost(…)

2:忽略某些测试类

@Ignore 注解应该附上相关 issue 地址,方便后续开发者追踪了解该测试被忽略的历史原因。

如 @Ignore(“see #1”)

3: try-catch 单元测试异常

当单元测试中的代码引发异常的时候,测试将失败,因此,不需要使用 try-catch 捕获异常。

@Test
public void testMethod() {
  try {
            // Some code
  } catch (MyException e) {
    Assert.fail(e.getMessage());  // Noncompliant
  }
}

你应该这样做:

@Test
public void testMethod() throws MyException {
    // Some code
}

4:测试异常情况

当你需要进行异常情况测试时,应该避免在测试代码中包含多个方法的调用(尤其是有多个可以引发相同异常的方法),同时应该明确说明你要测试什么。

5:拒绝使用 MockitoJUnitRunner.Silent.class

当单测出现 UnnecessaryStubbingException 时,请不要第一时间考虑使用 @RunWith(MockitoJUnitRunner.Silent.class) 来解决它,这只是隐藏了问题, 你应该根据异常提示解决相关问题,这并不是一个困难的工作。当完成更改时,你会发现,你的代码又简洁了许多。