该主题不但解释了如何修改自动生成的桩函数,而且解释如何添加用户定义的桩函数来代替在测试过程中无法(或不想)获得的外部资源调用。有关桩函数及其在开发过程中如何帮助您测试软件的背景信息,请参见桩函数。 

各节内容包括:

注释

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

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

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

访问桩函数视图

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

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

当第一次打开桩函数视图时将显示为空,并且将显示“未收集符号数据 ”的消息。 



理解桩函数视图

桩函数视图中包括以下栏目:

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

更新

桩函数视图中的内容将根据单元测试期间收集的数据进行更新,并响应于桩函数视图中可用的特定操作(即创建用户桩函数,生成自动桩函数)。值得指出的是,用户进行的外部操作的响应并不会使桩函数视图更新内容(手动添加/删除桩函数等)。

未使用定义

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

多重定义

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

收集/刷新符号数据

如需收集或刷新符号数据:

  1. 在项目树状结构中,选中将测试文件。
  2. 运行将构建测试可执行文件的测试配置(例如,“收集桩函数信息”或“构建测试可执行文件”测试配置)。

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

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

  1. 跳转到【桩函数视图】菜单,然后选择桩函数设置...以打开配置对话框。
  2. 自动生成的桩函数输出位置字段中指定将保存自动生成的桩函数的位置。


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

使用桩函数视图的提示

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

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

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

  1. 如果尚未这样做,请为桩函数创建另一个目录。
    • 桩函数目录可以位于项目中的任何位置。默认情况下,C/C++test 期望桩函数存储在项目的桩函数目录的子目录中。然而,您可以使用其他位置,只要您相应地修改【测试配置】的 使用设置中找到的文件中的多余符号 (在 执行> 符号 选项卡中)即可。

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

                a.选择 文件> 新建> 文件夹 (如果不可用,选择 文件> 新建> 其他, 选择 常规> 文件夹, 然后单击 下一步)。

                b.单击 高级 按钮。

                c.启用 链接至文件系统中的文件夹 选项。

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

                e.点击 完成

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

    注释

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

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

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

要为给定库中的所有函数快速添加桩函数,请按位置对表进行排序,以便更轻松地从该库中选择所有函数。

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

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

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

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



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

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

  8。输入桩函数数体/定义。如 C++test API Functions for User-Defined Stubs所述,用户定义的桩函数可以与 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/C++test API 函数 C++test API Functions for User-Defined Stubs)。

要自定义这些桩函数或者桩函数模板:

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

      • 您可以使用Tab 键在 ret_type、作用域、名称和参数之间移动。
  3. 根据需要修改桩函数主体/定义。如C++test API Functions for User-Defined Stubs所述,用户定义的桩函数可以与 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