本主题说明如何从 C/C++test 运行的测试中查看覆盖率信息。可以跟踪单元测试以及在应用程序级运行的手动或自动测试的覆盖率。 

查看覆盖率统计有助于衡量当前测试套件的覆盖率,并决定应该添加哪些额外的测试用例。C/C++test 可以报告各种类型的代码覆盖率,包括函数、行、路径、基础块、判定(分支)、简单条件和 MCDC 覆盖率指标。

章节目录:

在 GUI 中分析覆盖率

覆盖率概要

在 C/C++test 执行单元测试用例后查看覆盖率信息概要的步骤:

  1. 打开覆盖率视图。
    • 如果界面中没有该视图,可选择 Parasoft> 显示视图> 覆盖率打开。
  2. 从覆盖率视图工具栏的下拉菜单中选择类型> [所需覆盖率类型]。

项目以及所有分析的文件和函数的覆盖率统计信息都将显示在覆盖率视图中。如果测试执行后没有显示覆盖率,请确认 1)已启用覆盖率,2)显示的覆盖率类型与配置 C/C++test 计算的类型一致。该视图的工具栏会指示正在显示的覆盖率指标。

您可以使用 GUI 中的控件对结果进行排序和搜索。

带注释的源代码

在编辑器中打开被测文件时,C/C++test 将用绿色高亮显示被覆盖的代码,并用粉色高亮显示没有被覆盖代码。不会突出显示未执行的行。

请注意,您需要双击某个函数才能启动路径覆盖率视图。

注意

C/C++test 可计算的覆盖率元素(路径、基础块等)的数量是有限制的。当给定级别(函数、文件或项目)的实际元素数量超过限制(2147483647)时,C/C++test 在报告中将针对给定元素显示“N/A”。在该情况下,覆盖率视图会包含相应的消息,例如 [没有路径]、[没有基础块]。

从覆盖率元素到测试用例的回溯

了解与每个覆盖率元素相关的测试用例可以帮助您更好地评估如何扩展测试用例以提高覆盖率。

查看哪些测试用例与覆盖率元素相关的步骤:

  1. 在编辑器中选择覆盖率项。
    • C/C++test 将考虑当前所选项/光标位置,而不是鼠标指针位置。
  2. 右键点击所选项,然后选择 Parasoft> C++test> 显示已覆盖元素的测试用例。所有相关的测试用例都将在测试用例浏览器中突出显示。

特定测试用例的覆盖率数据

分析单个测试用例覆盖率的步骤:

  1. 在测试用例浏览器中,选择要分析覆盖率的测试用例。
    • 默认打开测试用例浏览器。如果 UI 中没有该视图,可以选择 Parasoft> 显示视图> 测试用例浏览器打开。有关了解和使用测试用例浏览器的详细信息,请参阅浏览 C++test 用户界面
  2. 在覆盖率视图中,点击与测试用例浏览器中的选择同步过滤器(双齿轮按钮)。

覆盖率统计信息和覆盖率高亮显示(在代码编辑器中)将仅针对选定的测试用例推算/呈现。

提示:使用工具栏按钮和菜单查看覆盖率

覆盖率视图提供的按钮和菜单命令可帮助您查看报告的覆盖率详细信息。

使用工具栏右侧的按钮可以折叠、删除、搜索结果以及与当前测试用例浏览器中的选择同步。

此外,使用以下工具栏按钮可以显示/隐藏覆盖的元素、显示/隐藏未覆盖的元素、高亮显示下一个路径(仅针对路径覆盖率)和高亮显示上一个路径(仅针对路径覆盖率)。

使用下拉菜单提供的命令可以按名称或覆盖率对结果进行升序/降序排序,以及选择所需的覆盖率类型。

注意:启动时的覆盖率结果

为优化性能,覆盖率视图以未初始化模式启动,仅显示项目级覆盖率摘要。此模式由灰色项目图标表示:

此模式下的覆盖率可能不是当前的覆盖率,例如源代码已在 IDE 之外修改的情况。您可以简单地通过展开项目节点来更新覆盖率视图。或者,您可以等待视图在执行期间自动更新。

分析报告中的覆盖率

有关配置、生成和查看报告的详细信息,请参阅查看结果

了解覆盖率类型

C/C++test 支持以下覆盖率类型:

  • 行覆盖率
  • 语句覆盖率
  • 基本块覆盖率
  • 路径覆盖率
  • 判定(分支)覆盖率
  • 修改条件/判定覆盖率(MC/DC)
  • 简单条件覆盖率
  • 函数覆盖率
  • 调用覆盖率

为帮助您了解 C/C++test 如何处理这些覆盖率类型,请务必阅读并理解下表中的术语:

概念说明
基本块一组由非分支语句组成的序列;没有控制流程分支的线性代码序列。
路径从函数入口到出口点的一组唯一的基本块序列。
判定/分支判定/分支是代码中的分支点可能执行控制流程判定。C/C++test 将 if-else、for、while、do-while 和 switch 指令视作分支点。C/C++test 不考虑像异常处理程序(throw-catch 语句)这样的动态分支点。
布尔表达式

对于 C++,布尔表达式只是具有“bool”类型的表达式。

而对于 C,C/C++test 将以下内容视为布尔表达式:

  • 具有非布尔参数的关系运算符(<、<=、==、>、>=、!=)。
  • 布尔运算符(||、&&、!)的每个参数。
  • if、for、while 和 do while 指令中的条件。
  • ? 运算符中的条件。

另请参阅在 C++ 模板中推算布尔表达式

MC/DC 判定由条件和零个或多个布尔运算符组成的顶级布尔表达式。C/C++test 针对源代码中除构造函数初始化器和函数默认参数之外的所有布尔非常量表达式推算 MC/DC和 SCC。
条件

原子布尔非常量表达式,是 MC/DC 判定的一部分。

如果 MC/DC 判定的子表达式不包含布尔运算符(&&、||、!),则被视为条件。

如果一个给定的原子表达式在一个判定中出现不止一次,那么每次出现都是一个不同的条件。

函数覆盖率

指示源代码中有多少函数在执行期间至少被访问过一次。如果所有函数至少被执行一次,则表示达到完整的 100% 函数覆盖率。


调用覆盖率

指示在程序运行时执行了多少个已定义的函数或方法调用。如果执行了所有函数或方法调用,则表示达到完整的 100% 调用覆盖率。 

限制:

  • 构造函数调用(无论显式还是隐式)都不包括在调用覆盖率计算中。
  • 隐式析构函数调用不包括在调用覆盖率计算中。
  • C++ 运算符(无论预定义还是重载)都不包括在调用覆盖率计算中。

示例:

class A
{
public:
    A() {}
    A(const A&) {}
    A(int val) {}
    A& foo() 
    {
        return *this;
    }
    ~A() {}
};


void funcA(const A& a) {}
A& operator+(const A & a, const A & b) {}


void defaultConstructorCall()
{
    A objA;                // Default constructor call - not included
    A objArr[10];          // Default constructor calls - not included
    A objB = objA;         // Copy constructor call       - not included
    A objC = 10;           // Implicit call of A(int) constructor - not included

    funcA(10);             // Implicit call of A(int) constructor - not included

    A res = objA + objA;   // Operator + call - not included 
						   // Copy constructor call - not included

    A* objD = new A();    // Default constructor call - not included
						  // Operator new call - not included

    delete objD;          // Destructor call - not included
						  // Operator new call - not included

    A* objE = new A[10];  // Default constructor calls  - not included
                          // Operator new call - not included

    delete[] objE;        // Destructor calls - not included
                          // Implicit destructor calls - not included
}



行覆盖率

指示有多少可执行源代码行被控制流执行至少一次。如果所有可执行的行都至少被执行一次,则表示达到完整的 100% 行覆盖率。


语句覆盖率

指示有多少可执行源代码语句被控制流程执行至少一次。如果所有可执行语句都至少被执行一次,则达到完整的 100% 语句覆盖率。


基本块覆盖率

类似于行覆盖率——不同点在于,对于基本快覆盖率,被测代码的单位是一个基本块(请参阅上表中该术语的定义)。该覆盖率指示源代码中有多少基本块被控制流执行至少一次。


路径覆盖率

指示给定函数中的每个路径是否都被控制流程执行。用于区分路径(请参阅上表中对该术语的解释)的分支点与判定(分支)覆盖率中的分支点相同。

由于循环会引入无限数量的路径,因此该度量方法仅考虑有限数量的循环可能性。C/C++test 针对 while 循环和 for 循环考虑两种可能性:不重复和至少重复一次。

由特殊情况(如从调用的函数抛出的信号或异常)产生的执行路径不包括在可能的执行路径集合中。如果这样的路径在运行时出现,它们将被计算并报告为“意外”路径。

在源文件编辑器中,C/C++test 一次高亮显示一条路径。要在源文件编辑器中查看路径,可双击覆盖率视图中的相应函数节点。若要在函数路径之间切换,可使用覆盖率视图工具栏中的高亮显示下一个元素高亮显示上一个元素按钮。

若想让 C/C++test 仅显示未覆盖的路径,可禁用覆盖率视图工具栏中的高亮显示已覆盖的元素按钮。若想让 C/C++test 仅显示已覆盖的路径,可禁用高亮显示未覆盖的元素。需注意,仅当禁用高亮显示未覆盖的元素按钮时,高亮显示下一个/上一个元素按钮才能在非预期路径间循环。



判定(分支)覆盖率

指示源代码中有多少分支被控制流程执行。如果取得所有分支点上每个判定的所有可能结果至少一次,则表示达到完整的 100% 覆盖率。

C/C++test 将源代码中以下语句类型视作分支点:if-elseforwhiledo-whileswitch。C/C++test 不考虑像异常处理程序(throw-catch 语句)这样的动态分支点。另请参阅在编译时已知判定结果时忽略判定/分支点

如果文件中没有判定,C/C++test 报告该指标不可用(使用“N/A”标签)。


修改条件/判定覆盖率(MC/DC)

MC/DC 符合国际技术标准 DO-178B/C (RTCA),该标准规定了航空业内关键任务设备和系统的软件认证标准。其中包含项实时嵌入式系统。

根据 DO-178B/C 标准,要达到完全(100%)的 MC/DC 覆盖率,必须满足以下三个条件:

    1. 每个判定都至少出现所有可能的结果一次。
    2. 判定中的每个条件都至少出现所有可能的结果一次。
    3. 判定中显示的每个条件都独立影响该判定的结果。

由于 C/C++test 认为每个条件和判定针对 MC/DC 覆盖率只有两种结果(true 或 false),因此只检查上面列出的第三个选项(c)——(c)暗含了条件(a)和(b)。通过仅改变一个特定条件并保持其他所有条件不变来对判定的结果独立产生影响。为测试一个给定的条件,C/C++test 将寻找满足以下要求的测试用例:

  • 被测条件有 true 和 false 结果。
  • 判定中的其他条件不变(或不会被评估,因为 C/C++ 中的运算符是短路逻辑运算符)。
  • 判定的结果会变。

因此,为计算 MC/DC 比率,C/C++test 使用以下公式

MC/DC[%] = m/n

其中 m 是经证明能够独立影响判定结果的布尔条件的数量,n 是判定中条件的总数。

需注意,要覆盖单个条件,至少需要执行两个测试用例:一个条件评估为 true,另一个条件评估为 false

另请参阅在 C++ 模板中推算布尔表达式

MC/DC 示例

例如,考虑以下代码:

   if (a && (b || c))
	// [...]

此处有三个简单的条件:'a'、'b' 和 'c'。因此,n(m/n MC/DC 公式中)等于 3。

现在,我们假设执行了以下测试用例:

idabca && (b || c)
1truetruefalsetrue
2falsetruefalsefalse
3truefalsefalsefalse

为计算 MC/DC,C/C++test 将寻找满足以下要求的测试用例对:1)给定判定的结果为不同的值;2)只有一个条件的值发生变化(所有其他条件保持不变)。

在我们的示例中有一对测试用例,其中独立改变 a 的值会影响整个判定的值:

idabca && (b || c)
1truetruefalsetrue
2falsetruefalsefalse

另一对测试用例显示,独立改变 b 的值会影响整个判定的值:

idabca && (b || c)
1truetruefalsetrue
3truefalsefalsefalse

这意味着我们的测试用例证明了 a 和 b 均独立影响整个判定的值。在 MC/DC 覆盖率方面,这两个值均被覆盖,而 c 条件未被覆盖。

C/C++test 将报告此示例的 MC/DC 覆盖率为 67% [覆盖 2/3 的条件]。将光标放在条件上方,可以在工具提示中查看条件和判定结果的实际值。

简单条件覆盖率

指示所有判定条件的结果的覆盖率。判定的结果总数等于 2*n,其中 n 是判定中的条件数。因此,要达到 100% 的覆盖率,所有条件都必须取得所有可能的结果。然而,要达到非零覆盖率,一个条件只需要取得一个结果即可。

另请参阅在 C++ 模板中推算布尔表达式

每个条件都是布尔条件。如果结果为 true 和 false,将用绿色标记。如果结果为 true 或 false,将用黄色标记。如果结果不为 true 或 false,则用粉红色标记。

若要查看条件的实际布尔值,可将光标放在条件上方。该值将在工具提示中显示。

在编译时已知判定结果时忽略判定/分支点

如果启用在编译时已知判定结果时忽略判定/分支点选项(请参阅执行选项卡设置 - 定义测试的执行方式),在编译时已知判定结果的情况下,C/C++test 将忽略判定的分支点.此设置将影响判定(分支)覆盖率和路径覆盖率。

请参阅以下示例:

constexpr bool constexpr_call(int a)
{
    return a >= 10;
}

void ingore_decision_known_outcome()
{
    int a = 0;

    do {
        ++a;
    } while (false);                    // decision / branching point ignored

    while (true) {                      // decision / branching point ignored
        break;
    }

    if (false) {                        // decision / branching point ignored
        a = 1;
    } else {
        a = -1;
    }

    if (constexpr_call(1)) {            // decision / branching point ignored
        a = 1;
    }

    const int cond = constexpr_call(1);
    if (cond) {                         // decision / branching point ignored
        a = 1;
    }
}


在 C++ 模板中计算布尔表达式

如果判定内的子表达式的类型取决于 C++ 模板参数,C/C++test 始终会假设此类子表达式无法再进一步分解为布尔子表达式。这是因为模板参数的实际类型可能无法转换为布尔类型(或者不同类型的分解可能不同)。这会影响将整个表达式分解为判定和条件的方式。

请参阅以下示例:

struct D {
  operator bool() { return false; }
};

struct E {
  bool operator||(const bool& b) { return b; }
};

template<typename T> void example(bool a, bool b, D d, E e, T t) {
  if (a || b) {}      // a || b
                      // 1 decision ('a || b'), with 2 conditions ('a' and 'b')

  if (d || b) {}      // d || b
                      // 1 decision ('d || b'), with 2 conditions ('d' and 'b')

  if (e || b) {}      // operator||(e, b)
                      // 1 decision ('operator||(e, b)'), with 1 condition ('operator||(e, b)')
                      //   not decomposable ('E' cannot be converted to 'bool')

  if (t || b) {}      // operator||(t, b)
                      // 1 decision ('operator||(t, b)'), with 1 condition ('operator||(t, b)')
                      //   assumed not decomposable ('T' may be 'E')

  if (a || b || t) {} // operator||(a || b, t)
                      // 2 decisions:
                      //   * decision 'operator||(a || b, t)', with 1 condition ('operator||(a || b, t)')
                      //     assumed outer OR is not decomposable ('T' may be 'E')
                      //   * decision 'a || b', with 2 conditions ('a' and 'b')
}

提高覆盖率

改进测试覆盖率中详述了优化覆盖率的策略。

  • No labels