使用覆盖率来度量测试套件(Test Suite)的质量
有两种比较流行的测试覆盖率的度量方法:
代码覆盖率
分支覆盖率
覆盖率度量会显示一个测试套件(Test Suite)会执行多少代码,范围从 0 至 100%。
除了上述两种方法之外,还有很多其它的度量方法。它们都是被用来评估一个测试套件的质量的。
通常人们会认为,这个覆盖率越高越好。不幸的是,这没那么简单,覆盖率度量确实能提供非常有价值的反馈,但是它们无法有效的评估出测试套件的质量。这一点与代码可进行单元测试的能力一样:覆盖率度量是一个非常好的负向指示(能评估出测试套件是否较差),但它无法评估出测试套件是否很好。
代码覆盖率度量
第一种,也是使用最多的度量方式就是代码覆盖率(code coverage,也叫测试覆盖率 test coverage)。它的公式如下:
例如:
该测试对上面的有效代码(共 5 行,8、9、10、12、13)的覆盖率达到了 80%(8、9、12、13)。
如果我重构一下代码,如下图所示呢:
可以看到有效代码行数变成了 3 行(8、9、10),它们都是绿色,说明都被覆盖了。从工具也可以看到覆盖率变成了 100%:
所以,代码覆盖率确实变化了,达到了 100%。但是测试套件的质量提高了吗?当然没有,我只是在方法内部重新罗列了一下代码,而测试仍然只验证了相同数量的可能结果。
分支覆盖率度量
另一种覆盖率度量方法是分支覆盖率。其公式如下:
想要使用复制覆盖率,你得想办法把代码里所有可能的分支求和,然后再检测测试走了多少分支。
例子:
还是之前的例子,IsStringLong 方法里面一共有两种情况,也就是两个分支。而测试只会运行小于等于 5 这种情况,所以分支覆盖率为 50%:
注意:分支覆盖率里面只考虑分支数量,并不考虑所执行的代码行数。
覆盖率度量的问题
尽管分支覆盖率的结果比代码覆盖率更有用一些,但是仍然无法依赖它们中的任何一个来决定测试套件的质量:
无法保证测试会验证被测试系统(SUT,System Under Test)所有可能的结果。
没有一种覆盖率度量法可以测试外部库的代码路径。
为了让代码路径真正被测试,而不仅仅是路过执行,单元测试必须有适当的断言。
例子:
这段代码其实产生了两个结果,分别是 13 和 15 行,只有第 15 行的结果被测试了,而第 13 行的结果(第 7 行的属性)并没有被验证。
而两种测试覆盖率的结果仍然没有变化,还是 100% 和 50%。
所以说,覆盖率度量无法保证代码会被测试,只能保证代码会被执行而已。
但是,如果你确实考虑了每种可能,同时结合分支覆盖率,那么是否能够提供一个可靠的机制,来判断测试套件的质量呢?很不幸,不能。
因为没有任何覆盖率度量方法能考虑外部库中的代码路径。
例如:
这个测试里,分支覆盖率达到了 100%,但是它没考虑 .NET 提供的 int.Parse 这个方法的可能结果。对于 int.Parse 至少有以下几种可能:
而我们只考虑了其中一种。所以您可能会遇到许多极端情况,并且无法查看测试是否将所有情况都考虑在内。
这并不是说覆盖率度量应该考虑外部库的代码路径(它们不应该),而是说明你不能指望这些度量来评估单元测试的质量好坏。覆盖率度量无法判断您的测试是否详尽无遗; 也不能说明是否有足够的测试。
特定的覆盖率数字
要把覆盖率度量当成一个指标,而不是一个目标。
就如同生病住院的病人,体温高可能表示发烧,它是一个有用的观察。但是医院不能把不择手段降低病人的体温当成治病的目标。
通常,如果系统核心某块的测试的覆盖率较高的话是很好的,但是如果把高覆盖率当成目标就不好了。
就个人而言:我认为覆盖率低于 60% 肯定不好,这说明有大量的代码未测试。但是更高的覆盖率数字并不能代表什么。所以度量覆盖率只是验证测试套件质量的第一步而已。