本主题说明改进单元测试覆盖率的策略。

各节内容包括:

理解低覆盖率的原因

使用几个预定义的度量来测试 C++test 的测试覆盖率(如查看覆盖率信息所述)。一般地,“改进测试覆盖率”的尝试应该旨在提高所有测试覆盖率指标。当这些指标会分别受到此处描述的技术的影响时,在每个用例中在给定技术应用与给定技术对给定覆盖率指标的影响之间可以做一个简单的逻辑连接。因此,为了简化,认为技术与行覆盖率是明确相关的。扩展到其他指标是不明确的。

增加测试覆盖率问题可以归纳为三个典型场景:

  1. 可用测试并不使用输入值,因为当执行所有分支的代码时,输入值会生成条件语句的控制表达式。
  2. 被测函数中的控制表达式取决于其它函数的返回值,这样当测试正在执行时取决于程序 /代码的状态。
  3. 可用的测试对测试下的代码未使用合适的设置,所以运行测试导致异常,并且中断异常点处的该执行流程。

由于控制表达式取决于代码的结构,所以它可以是次要的 (例如,每个函数的一个 if/else 语句)或非次要的(混有分支表达式的函数调用所返回控制表达式的嵌套循环条件)。

基于这些场景, C++test 框架中控制条件的不同技术,在每种用例中都是合适的。

增加代码覆盖率的策略

增加代码覆盖率的一般技术如下:

当您想改进测试覆盖率时,我们建议运用如下步骤:

  1. 检查相关(项目或文件)作用域的代码的覆盖率。
  2. 如果代码覆盖率在期望的水平以下,分析统计信息来排列文件或函数基于
    • 最低的代码覆盖率,或
    • 通过查看经过测试的代码可以判断出在增加工作覆盖率方面可能的 ROI。
  3. 对于所有按序排列的函数,请对所有阻碍覆盖率和它们条件值的控制表达式完成下列的步骤:
    1. 如果条件语句是一个直接的函数参数或一个函数类的数据成员,添加一个使用规定输入值的测试用例,该输入值使控制表达式对期望分支做出估值。
    2. 或如果条件语句是直接函数参数的简单函数或函数类的数据成员。添加创建测试对象的测试用例,然后设置已测试函数的数据成员和输入参数为指定值。且该指定值使的控制表达式对期望分支做出估值
    3. 或如果控制表达式看起来取决于复杂的对象(通过方法调用)。在适当状态中创建一个复杂测试对象(请参见下面的复杂对象)
    4. 或如果覆盖率块源于一个中断执行流程的异常
      • 检测代码找出抛出异常的原因
      • 如果抛出异常是由于不正确的函数/测试用例参数值(空指针解析引用等),创建/修改一个可以将正确值传送给函数的测试用例。
      • 如果指定对象的状态有问题,请参见下面的复杂对象。
      • 给抛出异常的函数创建一个用户桩函数,或者。
    5. 否则,如果条件是通过卷积代码序列在被测函数内计算的,则继续执行下一个函数。

测试驱动的桩函数通常适用于没有或几乎没有前提条件或参数的函数,也适合封装用户界面相互作用并返回代表用户操作的值的函数。这样的函数例子如 GUIWidget::whichButtonWasPressed()(比方 说)。

如果条件语句是函数的一个返回值,符号替换首选次序是:a) 初始函数 b) 用户桩函数。

用户桩函数

用户桩函数通常被编写为返回以下之一:

  • 每次调用时的值相同
  • 每次调用时的值不同
  • 取决于测试用例(测试驱动桩函数)名称的值

复杂对象

如果条件语句取决于复杂对象的状态(例如,对大量列表成员实行操作的函数,例如 List::containsElement(Element&)),那么该对象作为函数测试的前置条件需要放入适当状态。该用例中的两个重要条件是:

  1. 对象的期望状态是什么?
  2. 如何获得该状态?

只要理解了第一部分,C++test 测试用例中对象的期望状态一般可以通过以下方法获得:

  • 使用成员合理目标初始化(只对简单分类实用)。借助 C++test 工具,可以从测试用例的主体直接访问对象的所有私有数据成员。这样,您就能直接分配影响指定测试用例执行的数据成员了
  • 使用具有指定参数集的参数化构造函数来创建测试对象。
  • 使用在测试套件的设置方法中应用的指定初始化调用序列。该方法在测试对象总是需要非细节初始化时特别有效。使用设置方法允许您一次指定初始化序列,并自动将其应用于测试套件的所有测试用例中。
  • 使用测试对象工厂,该工厂将使用工厂类方法提供已知状态的测试对象。

使用 Coverage Advisor 增加覆盖率

Coverage Advisor 可以帮助您有效地实施上述增加代码覆盖率的策略。它可以分析代码以计算覆盖未发现的代码行所需的测试用例前提条件,从而帮助您创建用户定义的测试用例。有关详细信息,请参见使用覆盖率指导助手

  • No labels