C++|客户端单元测试实践—C++篇( 三 )


如果你的对象依赖了一些全局变量/静态变量 , 而且这些全局变量会在多个测试case使用 , 这种情况是比较难测试的 , 你不得不在每个测试用例结束之后手动重置全局变量 。 这样不符合单测测试的独立性原则 , 所以应该尽量避免使用全局变量 。
class MyTest {public: int GetIndex() { return index++;static int index; //静态变量;int MyTest::index = 0;TEST(test demo) { ASSERT_EQ(0 MyTest().GetIndex());TEST(test demo2) { ASSERT_EQ(0 MyTest().GetIndex()); //ErrorTEST(test demo) { MyTest::index = 0; ASSERT_EQ(0 MyTest().GetIndex());TEST(test demo2) { MyTest::index = 0; ASSERT_EQ(0 MyTest().GetIndex()); 迪米特法则
1、如果你代码中引入一些复杂的外部依赖 , 可以考虑将依赖转移给调用方
如:
class MyClass {public: void doSomething() { if(getUserManager().getUser(123).getProfile().isAdmin()) { //bad 复杂的依赖链 //xxxxelse {;class MyClass {public: void doSomething(bool isAdmin) { //简单的参数依赖 if(isAdmin) { //xxxxelse {; 2、直接依赖需要的参数 , 避免依赖类似于Context大而全的参数(可能非常难以构造)
如:
class MyClass {public: void processOrderBefore(const UserContextuserContext) { //修改之前 const Useruser = userContext.getUser(); const PlanLevellevel = userContext.getLevel(); const Orderorder = userContext.getOrder(); // ... processvoid processOrderAfter(const UserContextuserContext) { //修改后 const Useruser = userContext.getUser(); const PlanLevellevel = userContext.getLevel(); const Orderorder = userContext.getOrder(); processOrderAfter(user level order); //核心逻辑抽成新的函数void processOrderAfter(const Useruser const PlanLevellevelconst Orderorder) { //只需要对新封装函数进行单元测试即可 // ... process ; 封装分支逻辑
如果一个函数中分支太多 , 可以考虑将不同分支封装成不同的函数处理 , 然后对封装的函数分别编写单元测试用例 。
合理使用MOCK工具
考虑在以下场景使用mock工具 , 可以减少你的单元测试成本
代码中依赖的某个功能在你本次测试并不关心 , 如:db数据读取 , 发请求 测试用例依赖一些复杂的数据源 , 如:db数据读取 , 流水线上游数据 , 网络请求 一些非幂等性的函数调用或者结果返回不稳定的函数调用 , 如:随机数获取 , 时间获取 , db写入 对象的某些状态难以创建或者重现 , 如:网络错误或者文件读写错误 验证一些中间过程值 , 如:你的函数没有返回值 , 或者中间过程值不方便验证 , 可以mock中间某个函数调用来验证中间过程结果是否正确 尝试测试驱动开发(TDD)
如果你的需求所要实现的功能相对明确 , 那么可以先把接口定义出来 , 写一个最简单的实现运行起来 , 为其补充单元测试用例 , 然后再一步步完善具体实现细节 。
如果不能先写测试用例也没关系 , 重要的是在开发中尽早编写测试测试 , 不要将它们延迟到最后 , 这样可以及时重构你的代码 。

常见误区
只测试正常数据
应当尽量补充一些特殊值(如空值、边界值)或异常数据 , 以校验目标函数在不同的输入是否符合预期 , 尽量覆盖多的代码分支逻辑 。
结果校验不完整
如果你的目标测试函数中对属性进行了修改 , 那么应该尽可能校验这些修改是否符合预期 , 而不是单单只校验函数返回值 。
输入数据过于复杂
生成测试输入数据的代码应当避免与实际工程代码耦合 , 如:读取db或从流水线上游产生等 使用最小数据依赖的原则 , 只输入对当前测试用例会产生影响的数据即可 。如果数据源构造过于复杂 , 可以将一个大的测试用例拆分成多个小的测试用例 。测试代码存在分支条件