(图片来自:http://t.cn/R06rQHi)
引言
很多人看到这个标题时,都会想“你都100%代码覆盖了,怎么还会有问题呢?”
让我们看一下代码例子:
public class TestCalculator {
public Double add(Double a, Double b) {
return a + b;}
}
再看看用junit写出的测试代码:
@Test
public void testAdd() {
Double a = new Double(1);
Double b = new Double(2);
Double c = new Double(3);
assertEquals(c, testCalculator.add(a, b));
}
当我们使用EclEmma或者Jacoco来进行覆盖测试时,对于这个类,我们将得到100%测试覆盖率。
一切看起来都那么的完美,真是这样的吗?
好吧,让我们来来看看另一个测试,当其中一个变量为null时,返回值将会是什么?
@Test
public void testAddNullPointerException() {
Double a = new Double(1);
Double b = null;
Double c = new Double(3);
assertEquals(c, testCalculator.add(a, b));
}
好了,你会发现尽管覆盖率为100%,但程序却抛出了NullPointerException。
那么肯定有人会问,这样的话单元测试覆盖率的高低都不能作为衡量项目代码质量的指标,那么我们要单元测试还有什么用?
首先,我想我们可能搞错了测试覆盖的定义。
我们先听听Martin Fowler对于测试覆盖的定义:
Test coverage is a useful tool for finding untested parts of a codebase. Test coverage is of little use as a numeric statement of how good your tests are.
(图片来自:http://t.cn/R06jK5U)
他认为:把测试覆盖作为质量目标没有任何意义,我们应该把它作为一种发现未被测试覆盖的代码的手段。
当然,这应该是每个程序员毕生的追求之一,但是如果从项目角度考虑ROI(投入产出比),对于需要快速上线的短期项目,需要注重的是让测试覆盖核心功能代码。如果你的项目是一个长期项目,那么高覆盖率是非常有必要的,它意味着高可维护性,以及更少的bug。(前提是你的测试采用TDD/BDD方式编写,我见过将测试代码写的一团糟的人,看着他的代码,我宁愿重新写一遍。)
其实没有适用于所有项目的数值,每个项目都应有自己的阈值,但共性是,测试必须覆盖主要业务场景,代码的逻辑分支也必须尽可能的覆盖。
首先我们要阅读和理解项目代码,找出其中需要测试并且与业务强相关的代码,结合sonar等代码质量管理平台,从代码编写规范、复杂度、重复代码等方面进行代码重构,进一步提高项目的可维护性与可读性。
这也意味着重构,重构的同时,你需要更多的测试来保证你重构代码的正确性。
其次要对code coverage进行度量分析,那么我们应该怎么度量code coverage?
一般来说我们从以下四个维度来度量,如上图所示:
所以行覆盖率的高低不能说明项目的好坏,我们要从多方面进行思考,一般我们遵循的标准应是:函数覆盖率 > 分支覆盖率 > 语句覆盖率。
代码覆盖率最重要的意义在于:
单元测试的覆盖率并不只是为了取悦客户或者管理层的数据,它能够实实在在反应项目中代码的健康程度,帮助我们更好的改善了代码的质量,增加了我们对所编写代码的信心。