提高单元测试覆盖率的意义与价值


什么是单元测试覆盖率 ?

单元测试覆盖率是一种软件测试的度量指标,指在所有功能代码中,完成了单元测试的代码所占的比例。有很多自动化测试框架工具可以提供这一统计数据,其中最基础的计算方式为:

单元测试覆盖率 = 被测代码行数 / 参测代码总行数 * 100%

Note: 一般情况下, 参测代码总行数是指排除配置文件、以及测试代码本身的所有功能代码的总行数。


单元测试的度量方式

最常见的单元测试覆盖率的度量方式有以下三种:

1. 行覆盖率 / 语句覆盖
这种覆盖率统计方式是最为基础的,它可以用于体现参测代码中已被执行和未被执行的代码行(语句),从而可以进一步推断代码的逻辑覆盖是否全面。

2. 分支覆盖
这种覆盖率统计方式是用于统计代码中所有判断分支是否都被覆盖,如

...
if (condition)
{
    Operation_1();
} 
else 
{
    Operation_2();
}
...

语句就会根据 condition 的值产生两个不同的分支操作,那么在统计分支覆盖时,就需要对两个分支都进行校验。值得注意的是,上例中的代码,其分支覆盖可以被行覆盖所取代,也就是说若上面代码的行覆盖率为100%, 则其分支覆盖率亦为100%。

但是如果将代码换为三元表达式,如

...
condition ? Operation_1() : Operation_2();
...

此时,行覆盖率在统计时,只要这一行代码有被执行,那么行覆盖率必然为100%,并不关心是否两种情况都被执行过。但如此一来就没办法通过行覆盖率保证其分支覆盖率为100% 了。

3.条件覆盖
这种覆盖统计方式是针对代码中产生多分支,并且每个分支的激活条件比较复杂的情况。
代码的分支一般都是通过流程控制语句产生的,而流程控制语句再激活一个分支时是需要条件的,依然以之前的代码为例,若condition 是几个布尔值的组合,

...
if (Condition())
{
    Operation_1();
} 
else 
{
    Operation_2();
}
...

private bool Condition () {
  return condition_1 || condition_2 && condition_3;
}

那么此时,代码依然只有两个分支,但是由于激活条件是取决于condition_1, condition_2, condition_3的组合情况,那么在校验条件覆盖时,就应该考虑所有可能的组合情况,也就是8种测试用例。这样才可以使条件覆盖的覆盖率达到100%。全面考虑条件覆盖的做法就可以达到优化测试的效果

其实,覆盖率的统计方式还是用很多其他种类的,这里给出的只是几种最基础的。但不管怎样,完备的单元测试行覆盖都是其他度量方式的基础。当行覆盖率达标之后,开发人员才会有精力修改、优化测试用例,从而提高分支覆盖率、条件覆盖率等其他更具业务价值的单元测试覆盖率。


提高单元测试覆盖率的意义与价值

提高单元测试覆盖率的意义与价值_第1张图片
摘自《TestCoverage》Martin Fowler

有些时候,我们想提高项目测试覆盖率的阈值,比如从45% 提高到80%,又或者从 95% 提高到100%。
那么这个时候,我们就需要问一问自己,为什么要做这样的事情?提高覆盖率的意义与价值何在?

1. 是想通过单元测试来保证代码质量?
单元测试的覆盖率高与代码质量高,二者并没有直接关系,这是因为覆盖率的高低完全是可以“造假”的。一些没有实际业务价值的测试用例因运而生,甚至发生类似 AssertionFreeTesting 的情况,使覆盖率“虚高”
因此,我们没有依据说明高覆盖率就能确保好的代码质量。

2. 是想通过单元测试保证业务逻辑不会出错?
用单元测试保证业务逻辑,看上去很有道理。可是仔细一想,就会发现这一点,其实有些不切实际。一个业务功能的实现并不仅仅依赖于某一个方法、某一个类,那么通过单元测试能够保证的业务逻辑也是十分有限的,不可能做到“不会出错”
可以试想,如果仅仅依靠测试金字塔最底层的单元测试,使其覆盖率达标就能保证业务逻辑不出错,那么为什么还需要更高层的集成测试和功能测试,甚至探索性测试呢?
另一方面,如果是想将单元测试与业务逻辑相绑定,那么方向也不应是提高覆盖率,而应该是提供更加符合业务场景的测试用例给已有的单元测试,也就是提高已有的单元测试质量。

如此一来,这两个问题都无法通过提高单元测试覆盖率解决,那么我们提高覆盖率还有什么意义与价值呢?

测试覆盖率帮助我们找到没有被测试的代码
提高测试覆盖率,可以让我们在重构、优化这些没有被测试的代码时更有底气

但是,最大的前提是你的测试代码是值得信任的

那么现在,我们可以考虑一下这两种情况了:

  1. 将覆盖率从 45% 提高到 80%。
  2. 将覆盖率从 95% 提高到100%。

对于第一种情况,不到一般的初始测试覆盖率而言,首先会让开发人员在进行重构时很没有安全感,由于未被测试覆盖的代码量太多,从而无法快速发现改动是否会对未覆盖的代码带来不良影响。 因此,在这种情况下,提高代码的覆盖率,从而让更多的代码被测试,让开发者在重构时能更放心,这对项目更有益。

而对于第二种情况,可以看到覆盖率已经很高了,那么这个时候,
首先看看开发者经常改动或重构的代码是否都被覆盖,如果是,则重点应该放在优化测试用例上,看看是否所有的边界条件都被测试,所有的分支都被覆盖等等。因为这时,提高已有的单元测试质量,让测试用例更贴近业务场景所带来的收益会远远大于进一步提高测试覆盖率所带来的收益。
否则,一旦重构了未被覆盖的参测代码,就应该为改代码添加单元测试,使之被覆盖,从而确保功能的正常运作。


总结

  1. 单元测试仅用来保证代码所对应的功能正常,若想将之与业务结合,需要贴合业务场景的测试用例。
  2. 单元测试覆盖率仅用来找出未被测试的代码,若想通过覆盖率保证业务逻辑,需要进一步优化已有单元测试的质量。
  3. 提高单元测试覆盖率仅对之前没有测试覆盖或覆盖极其不足的代码有显著增益,若想对已经被测试高度覆盖的代码进行优化,需要着眼于提升已有的测试用例质量。
  4. 团队合作开发,应尽量保证自己每次提交的代码都已经全部被测试覆盖

参考文章

TestCoverage
AssertionFreeTesting
单元测试之覆盖率浅谈

你可能感兴趣的:(提高单元测试覆盖率的意义与价值)