在创建桩函数之前,桩函数 API 要求在桩函数视图中(请参阅在桩函数视图中配置桩函数选项)或直接在测试配置中(请参阅执行选项卡设置 - 定义测试的执行方式)勾选启用桩函数 API 选项

您可以针对与测试用例相关的特定需求动态配置桩函数行为。桩函数配置是使用专用 API 在源代码中定义的。您可以在测试套件的 setup/teardown、测试用例的 setup/teardown 以及测试用例内部指定桩函数配置。执行完测试用例后,将立即删除在测试用例内部创建的桩函数配置。同样,在测试用例和测试套件 setup() 函数内部指定了桩函数配置后,将在执行各自的 teardown() 函数后将其删除。

用户定义的桩函数行为在内部由“触发器对象”表示。定义触发器对象时,它会累积指定的一组操作、值和参数引用。触发器在执行时执行已定义的操作。触发器对象的执行会使用在定义触发器对象时按名称引用的参数的实际值。下表显示了用于指定和执行桩函数行为定义(触发器对象)的宏。 

定义
CPPTEST_ON_CALL(func-id)<definition>定义桩函数行为(创建触发器对象)。通常在测试用例主体中指定。
CPPTEST_AFTER_CALL(func-id)<definition>定义仅在对桩函数进行原始函数/方法调用之后执行的附加(可选)桩函数行为(创建触发器对象)。通常在测试用例主体中指定。
CPPTEST_ACTUAL_CALL(func-id)<args>执行为桩函数定义的行为。始终位于桩函数内部。
CPPTEST_ACTUAL_AFTER_CALL(func-id)<args>执行为原始函数/方法调用后处理定义的行为。始终位于桩函数内部。
  • definition:定义预期桩函数行为的桩函数 API 调用顺序
  • args:传递给桩函数行为执行的参数顺序
  • func-id:用于标识桩函数的字符串。默认情况下, C++test 生成的桩函数按照以下模式使用标识:<parent::><function_name>。仅当函数/方法是类或名称空间成员时,才添加 parent:: 前缀。仅添加直接父级。对于模板类方法、父级名称中省略了模板参数。

C++test 对函数的所有重载版本使用相同的桩函数标识符。如有需要,可以通过修改桩函数定义为每个重载的桩函数引入唯一的桩函数标识符。这样即可区分重载函数/方法的桩函数。

下表展示了与测试用例(C++ API)一起使用的桩函数主体的高度简化示例,以可视化数据和控制流。

测试用例定义桩函数定义
void testCase1 
{
 CPPTEST_ON_CALL("foo")
   If().Arg("param1").Equal(0).Fail("Problem").   
   Arg("__return").Assign(3);
 
   int __return =  goo(a);
   CPPTEST_ASSERT(__return == 5);
}
 
int foo(int param1, int param2);
int CppTest_Stub_foo (int param1, int param2){ 
  int __return = 0;   
  int __callOrig = 0;
 
  CPPTEST_ACTUAL_CALL("foo").
    WithArg("param1", ByRef(param1)).     
    WithArg("param2", ByRef(param2)).     
    WithArg("__return", ByRef(__return)).     
    WithArg("__callOrig", ByRef(__callOrig)).     
    End();
 
  if (_callOrig) {
    int _return = foo(a);
    CPPTEST_ACTUAL_AFTER_CALL("foo").
	WithArg("a", ByRef(a)).
	WithArg("__return", ByRef(__return)). 
	End();
    return __return;
  }
  return __return; 
}

在此示例中,foo() 函数的桩函数执行以下行为:

  • 测试 param1 的值是否为 0 并在 parasm1 等于 时报告问题消息。
  • __return 触发器参数分配整数值 3,该参数表示从桩函数返回的变量。

桩函数行为定义可以包括分配、逻辑运算、关系运算、算术运算和执行回调函数。有关桩函数行为定义语法的详细说明,请参阅桩函数 API 参考。C 和 C++ 语言的桩函数 API 有一些区别。语法说明和提供的示例中突出显示了所有差异。需注意以下差异:

  • 定义桩函数行为时用于调用方法/函数链的运算符对于 C 和 C++ 是不同的。API 的 C++ 版本使用点运算符(.),而 C 版本使用箭头运算符(→)
  • 在桩函数行为定义中指定值时,C 版本需要使用类型专用的方法来传递值,而 C++ 版本则允许使用单个重载方法。

上例中的桩函数行为定义使用 C 语言时具有以下形式:

CPPTEST_ON_CALL("foo")->If()->Arg("param1")->Equal()->Int(0)->Assert("Problem")-> Arg("__return")->Assign(3);

注意使用箭头运算符(->)而不是点运算符(.),以及使用 Int()函数调用传递整数值。

为桩函数行为定义指定参数

您可以在桩函数行为定义中按名称或按计算为参数名称的表达式来指定参数。在桩函数内部执行已定义的行为时,将提供命名参数的实际值,如前面关于 param1 和 __return 的示例所示。

下表展示了用于指定桩函数行为定义的参数的 API 方法:

API 方法:说明
Arg(const char *argName)

参数名称作为字符串提供。CPPTEST_ACTUAL_CALLCPPTEST_ACTUAL_AFTER_CALL 中使用的名称必须完全匹配。

示例:

[TestCase] CPPTEST_ON_CALL("foo").Arg("param1").Assign(0);
[Stub] CPPTEST_ACTUAL_CALL(“foo”).WithArg(“param1”, ByRef(“param1”).End();

ArgByExpr()

该参数通过对随后的表达式求值确定,该表达式应具有 const char * 类型。

示例:

CPPTEST_ON_CALL(“foo”).ArgByExpr().Arg(“argName”).Assign(0);

假设 {Arg(“arg_name”)} 将在桩函数执行期间求值为“char *”类型值,该值将用作参数名称,因此:

{ArgByExpr().Arg(“argName”)}=>{Arg(<value of Arg(“argName”)>)}

C++test 生成的桩函数对默认参数使用以下命名约定:

  • 桩函数参数(反映被打桩函数/方法的参数)可以按原始函数/方法声明中指定的名称使用
  • 用于从桩函数返回值的变量可通过 __return 名称使用
  • 用于作为条件将调用重定向到原始符号定义的变量可通过 __callOrig 名称使用

指定桩函数行为定义的值

可以“按值”或“按引用”提供操作的值,这在功能上与 C/C++ 中的概念相同。当需要左值行为时,必须通过对 ByRef() 函数的调用来包装传递的值。在需要右值语义的情况下,不必使用 ByRef() 来包装值。

通常,在期望赋值或需要将实际值的评估延迟到桩函数执行时,优先按引用传递变量。请参阅以下示例:

extern int global;
[By Value] CPPTEST_ON_CALL("foo").Arg("param1").Assign(global);
[By Reference] CPPTEST_ON_CALL("foo").Arg("param1").Assign(ByRef(global));

在第一个示例中,在处理 CPPTEST_ON_CALL 定义时复制分配给 param1 的值。执行赋值时,不会反映对全局变量的实际值的任何进一步更改。

在第二个示例中,在处理 CPPTEST_ON_CALL 定义时不会复制分配给 param1 的值。取而代之的是,将存储对它的“引用”(作为指针),并在执行分配时获取 global 的实际值。

在 C 和 C++ 模式下为桩函数行为定义指定值时,存在重要区别。在 C 模式中,指定值时必须总是用类型特定的函数包装。示例:

CPPTEST_ON_CALL("foo")->Arg("param2")->Assign()->Float(1.5);
CPPTEST_ON_CALL("foo")->Arg("param3")->Assign()->VoidPtr(ptr);

为所有简单类型提供包装函数。有关完整的列表,请参阅桩函数 API 参考。在 C++ 模式下,无需包装函数即可提供值。在所有需要指定值的情况下,您都可以使用通用的“Value”函数,该函数针对所有简单类型进行了重载。为简化表示法,重载的“操作符方法”版本可以直接接受值。示例如下:

CPPTEST_ON_CALL("foo").Arg("param1").Assign().Value(1); 
CPPTEST_ON_CALL("foo").Arg("param1").Assign().Value(1.77);
CPPTEST_ON_CALL("foo").If().Arg("p1").Greater().Value(1.77).Arg("p2").Assign(0);

使用重载的“操作符”方法的简单表示法:

CPPTEST_ON_CALL("foo").Arg("param1").Assign(1);
CPPTEST_ON_CALL("foo").Arg("param1").Assign(1.77);
CPPTEST_ON_CALL("foo"().Arg("p1").Greater(1.77).Arg("p2").Assign(0);

以下内容讨论典型的用例或桩函数 API。

示例 1: 调用原始函数

您可以从桩函数定义中调用原始函数/方法定义(假设原始定义在测试二进制文件中可用)。默认情况下,禁用对原始符号的调用。要启用此功能,需为命名参数 __callOrig 赋一个非零值。为此参数赋 0 值可禁用此功能。

如果启用了调用原始函数的功能,但不存在原始定义,则将在没有停止条件的情况下递归调用桩函数。这将导致堆栈溢出错误。

假设已为具有以下签名的函数配置桩函数:

int foo(int param1, int param2);

  1. 启用对原始函数的调用:

    [C++] CPPTEST_ON_CALL("foo").Arg("__callOrig").Assign(1);
    [C] CPPTEST_ON_CALL("foo")->Arg("__callOrig")->Assign()->Int(1);
  2. 您还可以根据参数值启用对原始函数的调用。如果将在参数 param1 设置为 的情况下调用桩函数,则会调用原始函数。在所有其他情况下,原始函数将不会被调用:

    [C++] CPPTEST_ON_CALL("foo").If().Arg("param1").Equal(0).Arg("__callOrig").Assign(1);
    [C] CPPTEST_ON_CALL("foo")->If()->Arg("param1")->Equal()->Int(0)->Arg("__callOrig")->Assign()→Int(1);

示例 2: 修改桩函数返回值

您可以修改桩函数返回的值。为此,C++test 生成的桩函数具有专用的名为 __return 的自变量。为该参数赋值可有效修改从桩函数返回的值。如果桩函数的返回值未修改,则返回默认值。

假设已为具有以下签名的函数配置桩函数

int foo(int param1, int param2);

  1. 您可以对桩函数的所有调用强制使用返回的整数值 5。

    [C++] CPPTEST_ON_CALL("foo").Arg("__return").Assign(5);
    [C] CPPTEST_ON_CALL("foo")->Arg("__return").Assign()->Int(5);

     

  2. 您可以根据桩函数参数值强制执行返回值。在以下示例中,当桩函数调用中的参数 param1 等于整数值 0 时,强制返回整数值 5。对于所有其他情况,将返回默认值(数值类型为 0)。

    [C++] CPPTEST_ON_CALL("foo").If().Arg("param1").Equal(0).Arg("__return").Assign(5);
    [C] CPPTEST_ON_CALL("foo")->If()->Arg("param1")->Equal()->Int(0)->Arg("__return")->Assign()->Int(5);
  3. 您可以调用原始函数并修改返回值。在以下示例中,将调用原始函数,并将返回值修改为整数值 5。

    [C++] CPPTEST_ON_CALL("foo").Arg("__callOrig").Assign(1).Arg("__return").Assign(5);
    [C] CPPTEST_ON_CALL("foo")->Arg("__callOrig")->Assign()->Int(1)->Arg("__return")->Assign()->Int(5);

示例 3: 检查桩函数调用参数的值

您可以针对预定义值测试桩函数调用参数的值。C++test 生成的桩函数具有专用的命名参数,它们表示桩函数调用参数。桩函数行为定义参数的名称与原始函数声明中使用的名称相同。

假设已为具有以下签名的函数配置桩函数:

int foo(int param1, int param2);

测试以确定桩函数调用参数 param1 是否等于 0。 如果不是,则测试失败,并显示所提供的消息。

[C++] CPPTEST_ON_CALL("foo").If().Arg("param1").NotEqual(0).Fail("Incorrect param value");
[C] CPPTEST_ON_CALL("foo")->If()->Arg("param1")->NotEqual()->Int(0)->Fail("Incorrect param value");

示例 4: 修改桩函数调用参数的值

与前面的示例一样,您可以为桩函数调用参数赋值。通常,在请求调用原始函数/方法时使用此方案。

假设已为具有以下签名的函数配置桩函数:

int foo(int param1, int param2);

  1. 您可以为桩函数调用参数赋值。在此示例中,将整数值 1 赋值给桩函数调用参数 param1

    [C++] CPPTEST_ON_CALL("foo").Arg("param1").Assign(1);
    [C] CPPTEST_ON_CALL("foo")->Arg("param1")->Assign()->Int(1);
  2. 您可以根据另一个参数为该参数赋值。在此示例中,如果 param1 的值等于整数值 0,则将整数值 1 赋值给 param2

    [C++] CPPTEST_ON_CALL("foo").If().Arg("param1").Equal(0).Arg("param2").Assign(1);
    [C] CPPTEST_ON_CALL("foo")->If()->Arg("param1")->Equal()->Int(0)->Arg("param2")->Assign()->Int(1);
  • No labels