代码覆盖率(Code Coverage)是反映测试用例对被测软件覆盖程度的重要指标,也是衡量测试工作进展情况的重要指标。它也是对测试工作进行量化的重要指标之一,测试工作往往不如开发那样激动人心,一个重要原因之一就是测试难于量化,而代码覆盖率恰恰是解决着一问题的重要指标。
根据其覆盖内容的不同,又可以细分为:语句覆盖、判定覆盖、条件覆盖、路径覆盖以及循环覆盖等等,这里有一篇很好的博客《代码覆盖率浅谈》介绍了各种不同覆盖率的定义。有的理解起来还是蛮拗口的,但其实不难,用到了再看就成!在所有这些覆盖中语句覆盖(Statement
coverage)是最简单的,但也是最常用的、最实际有效的覆盖率,Visual Studio采用的是语句覆盖中的基本块覆盖(Basic
block coverage)。
对于敏捷开发团队而言,代码覆盖率是每个Sprint要完成的硬性质量标准(Exit
Criteria)之一,覆盖率高低根据项目的不同而不同:75%,80%甚至100%都是可能的。代码覆盖率是一个白盒概念,应为毕竟它最后要落实到代码。既然代码覆盖率如此重要,那么什么时候该用它?该如何用它?
有人认为代码覆盖率重要,所以从项目的一开始就要进行代码覆盖率的检查和分析,即
获取覆盖率 –> 发现未覆盖的代码 –> 添加新测试用例。这样的使用方式,我把它命名为“代码覆盖率驱动的测试(CCDT,Code
Coverage Driven Test)”。CCDT看起来很美,理论上无懈可击,但实际操作则完全不是那么回事儿。先不说这种方式是否正确,单就由此引入的开销来说,就够项目组喝一壶的,呵呵!之所这样说,是因为CCDT需要经历:获取覆盖率、分析覆盖率和添加测试用例这三步,每一步都存在着很多潜在“副产品”开销,尤其是前两步。要获取覆盖率,需要执行所有的测试用例,而你知道现在业界70%的测试仍然是手动的,仅为了覆盖率就频繁的执行测试用例,显然是不现实的;频繁分析覆盖率结果也是一件耗时的工作,无论开发人员还是测试人员做都是如此,尤其是对于采用敏捷开发方法的团队,短迭代不允许引入如此劳神的工作内容。胡凯在他的博客中称这种唯覆盖率论症状为“测试覆盖率强迫症”,其中有一句描述很精彩:
测试覆盖率仅仅能够告诉团队什么没有被测试,根本就回答不了软件是否经过了有效测试!
我认为对于测试团队而言,测试的过程应该是用户场景覆盖驱动的测试(USCDT,User
Scenario Coverage Driven Test) ,即测试人员应该从用户真实使用场景出发,思考要测试的内容和设计测试用例。代码覆盖率是对USCDT的必要补充,以发现其中未覆盖的场景(Test
Hole)。代码覆盖应该是在项目/迭代的中后期引入,不用很频繁,点到为止,例如对一个3-4周的迭代,3次的覆盖率检查就已经足够了。当然,如果你的自动化测试比例比较高,采用持续集成(Continuous
Integration)的方式,例如:Visual Studio 2010中持续集成的构建功能,每次都能自动的进行代码覆盖率的统计,则可以更早,更频繁进行代码覆盖率,但前提一定是这不会带来过多的开销。
说了这么多,下面来点儿实际的吧,看看Visual Studio 2010中如何获取代码覆盖率数据!在Visual
Studio的集成开发环境中获自动化测试用例的码覆盖率数据是最简单的(采用命令行方式则稍复杂一些,但它还可以用来获取手动测试用例的覆盖数据),只需要下面三步:
步骤一 :在Test Settings配置中选择Code
Coverage
(Updated 2011/6/1) 这里需要注意,当选择Code Coverage项时,还要选择“Configure”按钮去配置一下要针对哪一个文件(.exe/.dl)收集覆盖数据,Visual
Studio会自动对这个文件进行instrument操作,否则是不会收集到覆盖数据的。
步骤二 :执行自动化测试用例
步骤三 :查看代码覆盖率结果
随着编程语言和开发工具的现代化和傻瓜化,现在获取和分析代码覆盖率工作越来越简单了,但人们对代码覆盖数据的使用却仍然停留在比较原始的阶段。其中还有很多值得去深入发掘的好东东,用于帮助测试团队优化测试过程和测试用例的设计!
代码覆盖从简到繁 – Block Coverage
自从上一篇博客 代码覆盖率 (Code Coverage)从简到繁 (一)以来,很久没有再写相关的内容了,今天再继续一篇,呵呵!我们知道,根据覆盖度量单元的不同,代码覆盖可以分为很多种,如:语句覆盖、判定覆盖、条件覆盖、路径覆盖等等,Visual
Studio(2005,2008 和 2010)系列工具中所采用的是语句覆盖(statement coverage),也被称为block
coverage。相对于其它覆盖而言,block覆盖操作和分析简单,是实际工程中比较常用的一种覆盖测试工具。那么究竟一个block是如何定义的呢?
block与代码行之间存在着对应关系吗?先来看看下面一个例子:
首先,自定义一个简单的C#函数GetInteger() 作为接下来被测试和代码覆盖收集的对象:
view plain
class Program
{
...
public int GetInteger(int arg1, int arg2)
{
if (arg1 > 0 && arg2 < 0)
{
return 0;
}
else
{
return 1;
}
}
}
然后, 针对这个GetInteger编写一个简单的单元测试用例,输入的数据组合为
(arg1=1, arg2=-1)
view plain
[TestMethod]
public void GetIntegerTest()
{
Program target = new Program();
int expected = 0;
int actual = target.GetInteger(1, -1);
Assert.AreEqual(expected, actual);
}
下面执行GetIntegerTest测试,看看这个用例到底覆盖了GetInteger中的哪些代码。执行的代码覆盖结果如下图所示:
Visual Studio使用不同的颜色来区分不同的覆盖结果:
- 淡蓝色:表示整行代码都被执行到了;
- 米色: 表示这行中的部分block被执行了;
- 红棕色:表示整行代码都没有被执行;
通过上面的米色未完全覆盖行 – if(arg1 >0 &&
arg2 < 0),我们可以看出同一行代码不一定属于同一个block,有可能一个代码行中包含了多个block。那么可不可以说同一个block的内容一定全在同一行上?要回答这个问题,我们可以先看一下Visual
Studio到底将GetInteger划分几个block。
通过上面的Code Coverage窗口中显示的覆盖数据结果,可以看出GetInteger一共被划分为
2 + 5 = 7 个block,其中未覆盖的block有2个。很显然,米色未完全覆盖行中肯定包含一个未覆盖block,那么剩下的一个只能可能是下面的“{”行和“return
1;”行。也就是说,“{” 和 “return 1;”这两行被认为是一个block,所以说同一个block的内容不一定全在同一行上,它有可能是分布在多行上,如下图所示:
综上所述,block和代码行之间没有非常规律的对应关系。那么到底block是如何定义的呢?MSDN:Code
Coverage Data Overview 给出了如下的定义:
A code block is a code path with a single
entry point, a single exit point, and a set of instructions
that are all run in sequence. A code block ends when
it reaches a decision point such as a new conditional
statement block, a function call, exception throw, enter,
leave, try, catch, or a finally construct.
简单的说,就是有个单一入口和单一出口的代码块就被认为是一个block,但是这样的定义实际上是很难和代码直接对应上。之所以这样,是因为Visual
Studio进行的instrument是针对IL或者(二进制)代码级别的而不是文本代码级别的。例如:GetInteger一共有7个block,能够直接从代码中识别出的有block
(1,2,3,4),如下图所示。而block 5中实际上是包括3个block,我等凡夫俗子单凭肉眼是很难准确区分出这3个block的。
在一般的代码覆盖应用中,我们不必关心的block到底是如何定义和划分的,代码覆盖的核心用途是要发现已有测试所遗漏的产品代码,而这些被测试遗漏代码在“彩色”代码窗口已经可以很清楚地找到。如果要真想了解你的代码是如何被划分block,办法也是有的:
针对.NET语言,ildasm.exe就是这样的工具,用它来查看instrumented后的IL代码,并和未instrument的IL进行比较,你就会发现其中的奥秘。此时,对照着上面给出的code
block的定义,可以更清晰地体会“单一入口和单一出口的代码块”的含义。在下一篇博客《代码覆盖从简到繁
(三) – 划分Block》中,将会花些时间具体介绍。 |