把文件读取、设备通信等外部调用替换为桩函数后,测试能够运行,断言却没有得到预期结果,有时单独执行通过,整组执行又失败。这类问题可能出在被测代码,也可能来自桩没有生效、输出参数没有赋值,或者测试前的状态不同。配置时应把测试驱动与桩函数分开考虑,驱动负责组织调用和检查结果,桩负责提供受控的依赖行为。以下以C/C++test Professional的Visual Studio集成环境、英文插件界面为例。
一、C/C++test如何配置桩函数与测试驱动
测试驱动需要明确输入、调用对象和预期输出。生成的测试骨架只是起点,默认的零值、空指针及断言值,都应按照接口要求重新核对。
1、建立测试套件并配置驱动步骤
测试框架会组织测试入口,通常无需另写一套主程序。用户主要维护测试用例中的前置条件、被测函数调用和断言。
①点击【Parasoft】→【Show View】→【Test Case Explorer】,选中目标项目。
②点击【Parasoft】→【Test Using】→【Builtin】→【Unit Testing】→【Generate Test Suites】。
③右击生成的套件,选择【Add New】→【Test Case using Editor…】,输入用例名称。
④在【Create test case for】中选择被测函数,创建用例,修改【Variables】、【Call】和【Assertions】并保存。
2、建立依赖桩并设置行为
①运行【Build Test Executable】收集符号信息,再点击【Parasoft】→【Show View】→【Stubs】。
②在【Stubs】视图菜单中打开【Stub settings…】,确认启用【Enable Stub Callbacks】。
③右击需要替换的依赖函数,选择【Create User Stub】,完成向导并保存桩文件。
④在测试用例中添加【Stub Configuration】,选择【Function】,配置返回值或输出参数,将该步骤移到【Call】之前。
桩文件提供可替换的函数定义,用例中的配置决定本次调用如何模拟。仅生成桩,不代表它已经具备测试需要的行为。以读取接口为例,返回“读取成功”之外,还可能需要写入缓冲区和实际读取长度,这些内容应与接口约定一致。
二、C/C++test外部依赖模拟后测试结果异常如何定位
排查时可以沿着一次依赖调用检查,确认用了哪个实现、收到了哪些参数、返回了什么数据。不要仅凭测试已经编译通过,就认定模拟过程正确。
1、确认执行的是预定桩函数
①重新运行【Build Test Executable】,在【Stubs】中找到目标函数。
②核对【Definition】和【Location】,区分【User】、【Original】与【Auto】,检查是否标有【conflict】。
③双击对应行,打开实际采用的定义,核对参数类型、命名空间及函数重载。
④桩文件位于自定义目录时,打开测试配置的【Execution】→【Symbols】,核对【Use extra symbols from files found in】。
视图反映的是最近一次测试构建收集的信息,修改配置后应刷新。自动桩的存在也不意味着它会替换原函数,原实现可用时可能仍被采用。出现冲突标记,则要检查是否为同一函数提供了多个用户桩定义。
2、核对返回值和输出参数
读取接口、查询接口经常同时返回状态码并填写数据。只修改状态码,后续代码仍可能拿到未初始化内容,异常就会出现在依赖调用之后。
①打开对应【Stub Configuration】,检查【Function】和选中的编辑模式。
②使用【Table】模式时,在【Action】中选择【SET VALUE】,在【P1】填写【*__return】,在【P2】填写返回值。
③核对输出指针、缓冲区及长度参数,补充符合接口要求的赋值。
④在【Assertions】中分别检查返回状态和输出数据,运行当前用例。
3、检查配置顺序与遗留状态
同一个桩可以被多次配置,后面的配置可能覆盖前面的行为。另一个容易遗漏的地方是静态变量、全局变量和模拟计数器,它们未必随着一次回调注册结束而恢复。
①在用例编辑器中,将【Stub Configuration】放到需要它的【Call】之前。
②检查同一函数是否存在多个【Stub Configuration】,核对每次调用前实际生效的设置。
③代码方式配置时,将【CPPTEST_REGISTER_STUB_CALLBACK】放在被测函数调用之前,核对桩标识。
④在套件编辑器的【TestCaseSetUp】和【TestCaseTearDown】中补充状态初始化与资源清理。
三、C/C++test桩函数修改后怎样验证测试有效性
修正模拟行为后,还应确认测试确实覆盖了原定场景。用例通过只能说明当前检查没有发现差异,断言是否充分、依赖是否按预期调用,也需要验证。
1、检查调用次数和连续返回场景
重复读取、失败重试等逻辑,通常不能用一个固定返回值完整模拟。某次调用应失败、后续调用应成功时,需要核对返回序列与实际调用次数。
①在用例中添加【Stub Expectations】,放在相关【Call】之前。
②在【Action】中选择【EXPECT NUMBER OF CALLS】,填写【Function】及【Parameters】。
③需要检查多个依赖的顺序时,选择【EXPECT CALL SEQUENCE】,填写需要监测的函数序列。
④连续调用需要不同结果时,在【Stub Configuration】中设置逐次调用值,或通过【stubCallInfo->callNo】配置对应分支。
2、分别运行正常、失败和组合用例
①为依赖成功、依赖失败及边界数据分别建立用例,核对各自【Variables】和【Assertions】。
②选中单个用例,运行【Run Unit Tests】,记录结果,再选中整个套件重复执行。
③单独通过、组合失败时,对照【TestCaseSetUp】、【TestCaseTearDown】及共享桩使用的变量,检查前序用例留下的数据。
④修正后重新执行【Build Test Executable】与【Run Unit Tests】,保留本次测试配置和结果。
验证失败场景时,断言应检查程序是否按要求报错、退出或保留数据,不能为了通过而沿用正常场景的期望值。必要时增加一条能够触发断言失败的对照输入,检查测试是否有能力发现被测行为的变化。
总结
C/C++test的桩函数与测试驱动配合使用,可以让难以稳定复现的外部条件进入日常测试,帮助开发人员检查错误处理和边界行为。模拟是否有参考价值,取决于它是否遵守真实接口的约定,以及测试是否检查了应当关注的结果。保留清楚的输入、依赖行为和预期输出,既便于复查异常,也方便团队维护已有用例。希望这些方法能帮助大家减少测试配置带来的误判。如有C/C++test相关需求,欢迎沟通咨询。