本主题说明如何添加用户自定义的桩函数,以替代在测试过程中无法访问(或不想访问)的资源调用,以及如何修改自动生成的桩函数。有关桩函数的背景信息及其如何帮助您在开发过程中测试软件,请参阅桩函数。 

章节目录:

注释

  • 在处理桩函数时,需确保您的测试配置的插桩模式设置(执行> 通用选项卡)已设为完全不带覆盖率的完全运行时或包括桩函数插桩的自定义选项。
  • C/C++test 按以下顺序对桩函数进​​行优先级排序:用户定义的桩函数、自动生成的安全桩函数、原始函数、自动桩函数。仅当没有其他定义(用户桩函数或原始桩函数)可用时,才使用自动生成的桩函数。
  • 如果原始定义在代码或库中的任何位置可用,C/C++test 不会对析构函数打桩。仅当原始析构函数不可用时,才会对析构函数使用 C++test 桩函数。

在桩函数视图中查看桩函数

桩函数视图基于最近一次运行的单元测试测试配置提供有关桩函数配置的详细信息。您可以通过为缺少的符号添加用户自定义的桩函数或自动生成桩函数来修改配置。

访问桩函数视图

选择 Parasoft> 显示视图> 桩函数以访问桩函数视图。

当第一次打开桩函数视图时

第一次打开桩函数视图时,除了显示“没有收集符号数据”消息之外,视图内容将为空。



理解桩函数视图

桩函数视图中包括以下内容:

  • 符号:函数或全局变量名。
  • 定义:当前定义/桩函数类型:
    • 用户:使用了用户提供的定义/桩函数。
    • 安全:使用了 C++test 的安全定义/桩函数。
    • 原始:使用了原始定义。
    • 自动:使用了 C++test 的自动定义/桩函数。
    • N/A (不需要):定义不可用,链接器不需要该定义。
    • N/A:定义不可用,但是链接器需要该定义(在许多情况下,这会在构建测试可执行文件时导致链接器错误)。
  • 位置:当前定义的位置(源文件、库,如未找到将显示 N/A)。

更新

桩函数视图中的内容将根据单元测试期间收集的数据以及在桩函数视图中的特定操作(即创建用户桩函数,自动生成桩函数)进行更新。需注意,桩函数视图内容不会因用户进行的外部操作更新(手动添加/删除桩函数等)。

未使用定义

桩函数视图能提供最近使用的桩函数配置的信息以及其他可用(但未使用)的定义。例如,桩函数视图能显示具有用户定义桩函数的函数现有的原始定义信息。对于这些定义类型,(未使用) 将显示在“定义”列,例如:原始 (未使用)。如需在桩函数视图中隐藏这类定义,可点击桩函数视图工具栏中的筛选按钮,然后勾选隐藏未使用的定义选项。

多重定义

如果同一个函数具有多个桩函数定义(例如某个函数具有两个用户桩函数定义),将会在桩函数视图中同时显示。在该桩函数图标上将标注错误图标并在“定义”列显示 (冲突),例如:用户 (冲突)。

收集/刷新符号数据

收集或刷新符号数据的步骤:

  1. 在项目树状结构中,选择要测试的文件。
  2. 运行用于构建测试可执行文件的测试配置(例如,“Collect Stub Information”或“Build Test Executable”测试配置)。

在桩函数视图中配置桩函数选项

桩函数视图允许指定自动生成的桩函数的保存位置,以及启用或禁用所需的动态桩函数配置模式(请参阅动态桩函数配置):

  1. 前往桩函数视图菜单,选择桩函数设置...以打开配置对话框。
  2. 自动生成桩函数输出位置字段中指定保存自动生成的桩函数的位置。


  3. 启用或禁用以下动态桩函数配置模式:
    • 启用桩函数回调 - 启用后,桩函数回调机制将用于动态桩函数配置;请参阅使用桩函数回调。默认情况下启用此选项。
               插入对原函数的调用 - 启用后,生成的桩函数将调用原始函数;请参阅创建调用原始函数的桩函数。默认情况下禁用此选项。
    • 启用桩函数 API(已弃用)-  启用后,桩函数 API 将用于动态桩函数配置;请参阅动态桩函数配置。默认情况下禁用此选项。

使用桩函数视图的提示

  • 要跳转至符号定义/桩函数(在代码编辑器中),可双击相关的表格行。或者,右键点击并从快捷菜单中选择定位(仅适用于位于当前项目文件中的定义)。
  • 要删除具有桩函数定义的文件,可在表中选择该文件,然后从快捷菜单中选择移除桩函数文件。将删除此文件中的所有桩函数定义。
  • 要按照某一列的值对表格进行排序,可点击该列的标题。
  • 要搜索表格内容,可右键点击表格,从快捷菜单中选择查找,然后指定搜索条件。

将用户定义的桩函数添加到向导生成的桩函数文件中

通过创建和编辑向导生成的桩函数文件来添加用户定义的桩函数:

  1. 首先为桩函数创建一个新目录。
    • 桩函数目录可以位于项目中的任何位置。默认情况下,C/C++test 期望桩函数存储在项目的桩函数目录的子目录中。但是,您可以使用其他位置,只要相应修改测试配置的使用从以下位置找到的文件中的额外符号执行> 符号 选项卡)即可。

      如果您不想在项目目录中存储桩函数,可以添加一个文件夹,链接到存储于文件系统中其他位置的文件。操作步骤:

                a. 选择文件> 新建> 文件夹(或者选择文件> 新建> 其他,选择常规> 文件夹,然后点击下一步)。

                b. 点击高级按钮。

                c. 启用链接到其他位置选项。

                d. 输入或浏览源文件的位置。

                e. 点击完成

  2. 您可以通过以下方式之一打开桩函数向导:
    • 在桩函数视图中,右键点击您想要为其创建桩函数的函数,然后选择创建用户桩函数
    • 在项目树中选择您的桩函数目录,右键点击所选项,然后在快捷菜单中选择新建> 其他。将打开一个向导。选择 C++test > 用户桩函数,然后点击下一步。在该向导的函数表中,选择您想要为其创建桩函数的函数,然后点击下一步

    注释

    • “没有收集符号数据”警告表明尚未收集所需的符号数据。有关如何收集/更新符号数据的详细信息,请参阅收集/刷新符号数据
    • “没有可为其创建桩函数的符号”警告表明没有可为其创建用户自定义桩函数的已知函数。
  3. 在“用户桩函数文件”对话框中输入新桩函数文件的名称和位置。 
  4. 点击完成。用户定义的桩函数文件将被创建并在代码编辑器中打开。C++test 将自动添加适当的定义和所需的 #include 指令。
  5. 根据需要检查/修改桩函数定义和/或 #include 指令。
  6. 保存修改后的文件。

有关用户定义的桩函数的其他信息

  • 可以一次创建多个函数。为此,需在表中选择多个函数,然后右键点击所选内容,然后从快捷菜单中选择创建用户桩函数。所有用户桩函数都将添加到相同的桩函数文件中。
  • 可以为任何函数创建。
  • 具有最高优先级。即使原始定义可用,也会使用用户定义的桩函数。
  • 无法为全局变量创建。应改用自动桩函数。

如需为给定的库中所有函数快速添加桩函数,可按照位置对表格进行排序,从而更轻松地从该库中选择所有函数。

将用户定义的桩函数添加到空的桩函数文件

将用户定义的桩函数添加到一个空的桩函数文件中的步骤:

  1. 您可以通过以下方式之一打开桩函数向导:
    • 在桩函数视图中,右键点击您想要为其创建桩函数的函数,然后选择创建用户桩函数
    • 在项目树中选择您的桩函数目录,右键点击所选项,然后在快捷菜单中选择新建> 其他。将打开一个向导。选择 C++test > 用户桩函数,然后点击下一步。  在该向导的函数表中,选择您想要为其创建桩函数的函数,然后点击下一步

  2. 不要在函数表中进行任何选择(以创建一个空的桩函数文件)。
  3. 点击下一步
  4. 输入桩函数文件的名称/位置。
  5. 点击完成。桩函数文件将在编辑器中自动打开。
  6. 通过键入 stub,将光标移至“stub”的“b”后面,按下 Ctrl + 空格,然后选择适当的模板(用于 C 或 C++ 函数的标准桩函数模板或构造函数/析构函数桩函数)来创建桩函数模板。



  7. 为 ret_type、范围、名称、参数插入适当的值。需注意,C/C++test 的桩函数(构造函数桩函数除外)采用与原始函数相同的值。

您可以使用 Tab 键在 ret_type、范围、名称和参数之间移动。

  8. 输入桩函数主体/定义。如用户定义的桩函数的 C++test API 函数中所述,用户定义的桩函数可以与 C/C++test API 函数交互作用。

 9. 保存修改后的文件。

从库(项目外部)中桩函数符号?

需确保按照以下步骤将测试配置的桩函数模式设置为对所有调用打桩

  1. 选择 Parasoft> 测试配置
  2. 选择您要使用的测试配置来执行这些桩函数的测试(或创建一个新的桩函数)
  3. 打开执行> 常规选项卡。
  4. 插桩模式下,选择自定义插桩
  5. 点击插桩模式右侧的编辑按钮。
  6. 在插桩功能对话框中,将函数打桩模式设置为对所有调用打桩

插桩虚函数调用 ?

在对虚函数调用进行打桩时,确保为给定指针或引用在编译时指向的类中的函数创建桩函数。例如,在以下代码中,标记为 (*) 的调用只有在为 Base::doSth() 方法创建桩函数时才会被打桩

    void example(Base* ptr)     
    {
	// ...
	ptr->doSth();  // (*) 
	// ...
   }

指针所指向对象的实际运行时类型并不重要。

要创建运算符的用户桩函数,可使用下面的桩函数名称:

运算符函数名称
newCppTest_Stub_operator_new
deleteCppTest_Stub_operator_delete
new[]CppTest_Stub_operator_array_new
delete[]CppTest_Stub_operator_array_delete
+CppTest_Stub_operator_plus
-CppTest_Stub_operator_minus
*CppTest_Stub_operator_star
/CppTest_Stub_operator_divide
%CppTest_Stub_operator_remainder
^CppTest_Stub_operator_excl_or
&CppTest_Stub_operator_ampersand
|CppTest_Stub_operator_or
~CppTest_Stub_operator_or
!CppTest_Stub_operator_not
=CppTest_Stub_operator_assign
<CppTest_Stub_operator_lt
>CppTest_Stub_operator_gt
+=CppTest_Stub_operator_plus_assign
-=CppTest_Stub_operator_minus_assign
*=CppTest_Stub_operator_times_assign
/=CppTest_Stub_operator_divide_assign
%=CppTest_Stub_operator_remainder_assign
^=CppTest_Stub_operator_excl_or_assign
&=CppTest_Stub_operator_and_assign
|=CppTest_Stub_operator_or_assign
<<CppTest_Stub_operator_shift_left
>>CppTest_Stub_operator_shift_right
>>=CppTest_Stub_operator_shift_right_assign
<<=CppTest_Stub_operator_shift_left_assign
==CppTest_Stub_operator_eq
!=CppTest_Stub_operator_ne
<=CppTest_Stub_operator_le
>=CppTest_Stub_operator_ge
&&CppTest_Stub_operator_and_and
||CppTest_Stub_operator_or_or
++CppTest_Stub_operator_plus_plus
--CppTest_Stub_operator_minus_minus
->*CppTest_Stub_operator_arrow_star
->CppTest_Stub_operator_arrow
()CppTest_Stub_operator_function_call
[]CppTest_Stub_operator_subscript
<?CppTest_Stub_operator_gnu_min
>?CppTest_Stub_operator_gnu_max
,CppTest_Stub_operator_comma

为缺失定义的符号生成桩函数

右键点击表格中的函数,然后从快捷菜单中选择自动生成桩函数,为缺失定义的符号生成自动桩函数文件。将创建一个自动桩函数文件并在编辑器中打开。C/C++test 将自动添加适当的定义和所需的 #include 指令。

您还可以同时为多个符号创建自动桩函数:

  1. 在表中选择多个函数,然后右键点击所选内容
  2. 从快捷菜单中选择自动生成桩函数。所有自动桩函数都将添加到同一桩函数文件中。

您也可以为没有可用定义的符号创建自动桩函数。对于其他符号,则改用用户桩函数。

自动桩函数具有最低优先级。如果有其他定义可用,则不会使用自动桩函数。

了解和自定义自动生成的桩函数

如上所述,C/C++test 可用于为缺失的函数和变量定义自动生成可自定义的桩函数。

自动生成的桩函数具有与用户定义的桩函数相同的功能,但是带有 CppTest_Auto_Stub_ 前缀(而不是 CppTest_Stub_ 前缀)。这使您可以为作用域中的同一函数使用多个桩函数。

如果 C/C++test 无法自动生成完整的桩函数定义,它将创建一个您可以自定义的桩函数模板(通过输入适当的 return 语句、添加 include 指令等)。在定义完整的桩函数之前,桩函数模板将保存在桩函数文件中。

仅当没有其他定义(用户桩函数或原始桩函数)可用时,才使用自动生成的桩函数。

您可以通过动态桩函数配置 API(请参阅动态桩函数配置)或使用测试用例编辑器桩函数步骤(请参阅使用步骤)来轻松配置自动生成的桩函数。

在极少数动态桩函数配置 API 或测试用例编辑器无法满足需求的情况下,您可以使用自定义逻辑实现完全替换生成的桩函数主体,例如 CppTest_IsCurrentTestCase 桩函数(请参阅用户定义的桩函数的 C++test API 函数)。

自定义这类桩函数或者桩函数模板的步骤:

  1. 打开相应的桩函数文件,该文件保存在测试配置的自动生成的桩函数的输出位置执行> 符号选项卡)指示的位置。
  2. 根据需要修改 ret_type、范围、名称和参数。
    • 需注意,C++test 的桩函数(构造函数桩函数除外)采用与原始函数相同的值。 

      • 您可以使用 Tab 键在 ret_type、范围、名称和参数之间移动。
  3. 根据需要修改桩函数主体/定义。如用户定义的桩函数的 C++test API 函数中所述,用户定义的桩函数可以与 C++test API 函数相互作用。
  4. 保存修改后的文件,然后重新运行分析。

禁用自动生成的安全桩函数

安全定义会自动生成,以替换“危险”函数。安全定义可用于大多数系统 I/O 例程(rmdir()、remove()、rename() 等)。如果使用了安全的定义,则不会调用原始定义——即使原始定义可用。我们建议您在条件允许时使用安全定义。使用这些定义可以防止在单元测试期间出现问题。这些桩函数不能被修改。

如果您不想使用自动生成的安全定义,可从测试配置的执行> 符号选项卡中的使用从以下位置找到的文件中的额外符号字段删除 ${cpptest:cfg_dir}/safestubs

如果要禁用特定函数的安全桩函数(并使用原始定义),可以编写用户桩函数,作为原始函数调用的包装器。示例:

int CppTest_Stub_mkdir(const char* p)
{
    return mkdir(p);
}

用户定义的桩函数的 C++test API 函数

void CppTest_Assert(bool test, const char * message)

该函数的工作方式类似于标准断言函数。只要“test”参数的值为 false,就会停止执行测试用例。作为测试用例结果,将报告“用户自定义断言失败”消息。此外,“message”参数的值将作为详细的失败说明,包括位置和堆栈跟踪详细信息。

void CppTest_Break()

此函数使您可以无条件停止测试用例的执行。以这种方式停止的测试用例将报告“用户自定义中断调用”消息。此外,还提供了位置和堆栈跟踪信息。

bool CppTest_IsCurrentTestCase(const char* id;

该函数允许查询当前执行的测试用例。如果指定的 id 等于当前执行的测试用例的名称,将返回 true,否则返回 false。此功能对于那些根据外部函数调用使用条件语句的函数非常有用。相关示例,请参阅由测试用例驱动的桩函数

bool CPPTEST_DS_HAS_COLUMN(const char* name) 

您可以用此函数查询用户/自动桩函数中的数据源列。请注意,数据源是特定于测试用例的,因此只有在给定测试用例的上下文中调用桩函数时,数据才可用(在全局初始化期间调用桩函数时,数据不可用)。

const char* CppTest_GetCurrentTestCaseName(); 
const char* CppTest_GetCurrentTestSuiteName();

这些函数可获取当前执行的测试用例和测试套件的名称。使用这些函数可以编写适用于特定测试套件中特定测试用例的桩函数。

相关示例,请参阅测试用例驱动的桩函数

在桩函数中使用数据源

您配置的用于在 C/C++test 中使用的任何数据源都可以在桩函数中使用。

要配置数据源,可参考添加数据源中的说明。

要在桩函数中使用数据源,可使用 bool CPPTEST_DS_HAS_COLUMN(const char* name) API 函数。您可以按以下方式查询桩函数中的数据源列:

int CppTest_Stub_goo (void) 
{
	if (CPPTEST_DS_HAS_COLUMN("stub_goo_return")) {
		return CPPTEST_DS_GET_INTEGER("stub_goo_return");
	} else {
		return 0; // Data Source not available
	}
}

请注意,数据源是特定于测试用例的,因此只有在给定测试用例的上下文中调用桩函数时,数据才可用(在全局初始化期间调用桩函数时,数据不可用)。

bool CPPTEST_DS_HAS_COLUMN(const char* name) 宏也可以与其他 API 函数结合使用—例如 CppTest_isCurrentTestCase()。

针对不同的上下文使用不同的测试和/或桩函数

请参阅针对不同的上下文使用不同的测试和/或桩函数

使用动态桩函数配置

请参阅动态桩函数配置

使用测试用例驱动的桩函数

C/C++test 允许您创建可由当前执行的测试用例“驱动”的桩函数。C/C++test API 提供以下函数:

bool CppTest_IsCurrentTestCase(const char* id);

可以在“用户桩函数”定义中使用此函数来查询当前执行的测试用例。如果指定的 id 等于当前执行的测试用例的名称,将返回 true,否则返回 false

此功能对于那些根据外部函数调用使用条件语句的函数非常有用。示例:

void foo()
{
  if (goo() == 1) {
     //code
  } else {
     //code
  }
}

要实现 100% 的行覆盖率,您可以为 foo() 函数创建两个测试用例,然后为 goo() 函数创建一个用户桩函数:

int ::CppTest_Stub_goo()
{
  if (CppTest_IsCurrentTestCase("TestCase1")) {
    return 1;
  } else {
    return 0;
  }
}

现在,该桩函数由测试用例“驱动”。在此示例中,返回值取决于当前执行的测试用例:取决于 TestCase1 测试用例。对于 TestCase1,将返回 1,对于任何其他测试用例,将返回 0。这样,您就可以针对 foo() 函数实现 100% 覆盖率。

对于编写由测试用例驱动的桩函数十分有用的其他 API 函数包括 const char* CppTest_GetCurrentTestCaseName(); 和 const char* CppTest_GetCurrentTestSuiteName();。这些函数可获取当前执行的测试用例和测试套件的名称。使用这些函数可以编写适用于特定测试套件中特定测试用例的桩函数。例如,以下桩函数对于名称包含“nomemory”且来自“AllocTestSuite”测试套件的所有测试用例的行为均不同:

#include <string> 
#include <stdlib.h>
EXTERN_C_LINKAGE void* CppTest_Stub_malloc(size_t size)
{
	std::string testSuiteName = CppTest_GetCurrentTestSuiteName();  
        std::string testCaseName = 	CppTest_GetCurrentTestCaseName();
        if ((testSuiteName == "AllocTestSuite") &&
	    (testCaseName.find("nomemory") != std::string::npos))
	{
	     // Simulate no memory situation. return 0; 
	}
    	return malloc(size);
}

为用户定义的桩函数文件指定自定义编译器选项

您可以为每个用户定义的桩函数文件设置自定义编译器选项(例如,使用指定的 C/C++test 标志),如指定自定义编译器设置和链接器选项中所述。

  • No labels