C++中编写UT单元测试用例时如何mock非虚函数?
GMock存在的问题
GoogleTest中的GMock官方给的示例一般都是针对virtual虚函数进行Mock的,通常先写一个抽象类的接口(里面的函数全部是纯虚函数),然后生产环境和测试环境的Mock类分别实现该接口,在生产环境使用具体类;然后测试环境使用Mock模拟类,模拟具体类的功能,编写函数的单元测试用例。这种情况一般在设计中设计好的话比较方便。
最近在实际工作中,基于C++ GoogleTest测试框架时,发现在针对某个类的非虚函数进行Mock时,由于之前的类都写好了,不方便针对原来的类重构,即不方便侵入式修改原有类的代码,并且要针对写UT的函数接口没有使用virtual关键字进行声明,发现GMock提供的示例程序基本上都是针对virtual虚函数的。如果对类的非virtual虚函数进行Mock,Google官方给了一个类模版参数的实现方法,具体可以参考Mocking Non-virtual Methods,如下图所示:


这种方法说白了是利用模版参数来分别根据生产环境和测试环境进行对象的依赖注入,从而达到使用模拟mock类来替换实际具体类的目的。但是这种方法也有一个弊端,即对原有类会产生侵入式修改,引入了模版参数,需要将原来的生产代码具体类比如Car car;变量声明改成CarWrapper<Car> c; 模版类的形式,如果很多地方都使用到了该类,替换重构起来就很麻烦了。可以参考Mocking non-virtual and free functions with gMock这篇博客,讲得相对来说比较清楚了。
最后发现Github上面有一个cpp-stub项目能解决我们的问题。

具体说明可以参考cpp-stub原理说明
使用cpp-stub开源项目针对非虚函数进行Mock
原理介绍
- 如何获取原函数的地址(addr_pri.h 、addr_any.h)
- 如何用桩函数替换原函数(stub.h)
支持情况
-
支持的操作系统 :
- Windows
- Linux
- MacOS(x86-64, printf '\x07' | dd of=test_function bs=1 seek=160 count=1 conv=notrunc)
-
支持的硬件平台 :
- x86
- x86-64
- arm32
- arm64
- arm thumb
- riscv32
- riscv64
- loongarch64
- mips64
- ppc64 (由chatgpt 生成,需要验证)
- s390x (由chatgpt 生成,需要验证)
- alpha (由chatgpt 生成,需要验证)
- sparc (由chatgpt 生成,需要验证)
- sw_64 (由chatgpt 生成,需要验证n)
-
支持的编译器 :
- msvc
- gcc
- clang
-
支持函数类型:
- 常规函数
- 可变参函数
- 模板函数
- 重载函数
- lambda函数
- 静态函数(使用 addr_any.h)
- 内联函数(通过编译选项)
- 类的构造函数
- 类的析构函数
- 类的成员函数
- 类的静态成员函数
- 类的成员虚函数(非纯虚函数)
- 类的虚函数并且重载
- 仿函数
- 类的私有成员函数(使用 addr_pri.h)
- 类的私有成员函数(cpp17)
- 动态库里的函数(使用 dlsym()获取)
单元测试相关说明
不能打桩
- 不能对 exit 函数打桩(部分系统调用)
- 不能对纯虚函数打桩, 纯虚函数没有地址
- 不能对 lambda 函数打桩, lambda 函数获取不到地址(但可以尝试使用 addr_any.h 接口获取地址)
- 不能对静态函数打桩, 静态函数地址不可见.(但可以尝试使用 addr_any.h 接口获取地址)
单元测试编译选项, linux g++可用的
- -fno-access-control
- -fno-inline
- -Wno-pmf-conversions
- -Wl,--allow-multiple-definition
- -no-pie -fno-stack-protector
- -fprofile-arcs
- -ftest-coverage
代码覆盖率, linux g++使用方法
lcov -d build/ -z
lcov -d build/ -b ../../src1 --no-external -rc lcov_branch_coverage=1 -t ut -c -o ut_1.info
lcov -d build/ -b ../../src2 --no-external -rc lcov_branch_coverage=1 -t ut -c -o ut_2.info
lcov -a ut_1.info -a ut_2.info -o ut.info
genhtml -o report/ --prefix=`pwd` --branch-coverage --function-coverage ut.info
代码覆盖率, Windows使用方法
OpenCppCoverage.exe --sources MySourcePath* -- YourProgram.exe arg1 arg2
接口介绍
stub.h
Stub stub
stub.set(addr, addr_stub)
stub.reset(addr)
addr_pri.h
Declaration:
ACCESS_PRIVATE_FIELD(ClassName, TypeName, FieldName)
ACCESS_PRIVATE_FUN(ClassName, TypeName, FunName)
ACCESS_PRIVATE_STATIC_FIELD(ClassName, TypeName, FieldName)
ACCESS_PRIVATE_STATIC_FUN(ClassName, TypeName, FunName)
Use:
access_private_field::ClassNameFieldName(object);
access_private_static_field::ClassName::ClassNameFieldName();
call_private_fun::ClassNameFunName(object,parameters...);
call_private_static_fun::ClassName::ClassNameFunName(parameters...);
get_private_fun::ClassNameFunName();
get_private_static_fun::ClassName::ClassNameFunName();
addr_any.h(linux)
AddrAny any //for exe
AddrAny any(libname) //for lib
int get_local_func_addr_symtab(std::string func_name_regex_str, std::map<std::string,void*>& result)
int get_global_func_addr_symtab(std::string func_name_regex_str, std::map<std::string,void*>& result)
int get_weak_func_addr_symtab(std::string func_name_regex_str, std::map<std::string,void*>& result)
int get_global_func_addr_dynsym( std::string func_name_regex_str, std::map<std::string,void*>& result)
int get_weak_func_addr_dynsym(std::string func_name_regex_str, std::map<std::string,void*>& result)
addr_any.h(windows)
AddrAny any //for all
int get_func_addr(std::string func_name, std::map<std::string,void*>& result)
addr_any.h(darwin)
没实现
使用步骤
根据官方文档https://github.com/coolxv/cpp-stub/blob/master/README_zh.md描述,使用cpp-stub开源项目针对非虚函数进行Mock,可以按照以下步骤:
- 在C++单元测试项目中引入stub.h头文件
- 然后针对类的非virtual虚函数,针对性的写一个模拟打桩函数(函数原型要保持一致),然后再按照如下方法调用:
cpp
Stub stub;
// addr为原函数名,addr_stub为打桩函数
stub.set(addr, addr_stub);
官方给类的非静态成员函数的示例代码如下:
cpp
//for linux,__cdecl
// g++ -g test_objetc_member_function_linux.cpp -std=c++11 -I../src -o test_objetc_member_function_linux
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
int foo(int a){
cout<<"I am A_foo"<<endl;
return 0;
}
};
int foo_stub(void* obj, int a)
{
A* o= (A*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(ADDR(A,foo), foo_stub);
A a;
a.foo(1);
return 0;
}
官方给的类的静态成员函数代码示例如下:
cpp
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
static int foo(int a){
cout<<"I am A_foo"<<endl;
return 0;
}
};
int foo_stub(int a)
{
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(ADDR(A,foo), foo_stub);
A::foo(1);
return 0;
}
官方给的类的成员虚函数(非纯虚函数)代码示例如下:
cpp
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
virtual int foo(int a){
cout<<"I am A_foo"<<endl;
return 0;
}
};
int foo_stub(void* obj, int a)
{
A* o= (A*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
typedef int (*fptr)(A*,int);
fptr A_foo = (fptr)(&A::foo);
Stub stub;
stub.set(A_foo, foo_stub);
A a;
a.foo(1);
return 0;
}
官方给的类的虚函数并且重载示例代码如下所示:
cpp
//for linux gcc
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
virtual int foo(int a){
cout<<"I am A_foo"<<endl;
return 0;
}
virtual int foo(double a){
cout<<"I am A_foo"<<endl;
return 0;
}
};
int foo_stub(void* obj, int a)
{
A* o= (A*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
typedef int (*fptr)(A*,int);
fptr A_foo = (fptr)((int(A::*)(int))&A::foo);
Stub stub;
stub.set(A_foo, foo_stub);
A a;
a.foo(1);
return 0;
}
参考资料
- gMock for Dummies
- Mocking Non-virtual Methods
- https://github.com/coolxv/cpp-stub
C++ unit test stub(not mock) and awesome.Surpported ISA x86,x86-64,arm64,arm32,arm thumb,mips64,riscv,loongarch64. - Mocking virtual functions with gMock
- Mocking non-virtual and free functions with gMock
- Google Test and Mock Platform, Complete Tutorial. Part 1: Google Test
- Google Test and Mock Platform - Part 2: GMock Matchers
- Google Test and Mock Platform - Part 3: Mocking Classes and Functions with GMock
- C/C++单元测试如何解决非虚函数对象依赖-使用CppFreeMock
- https://github.com/gzc9047/CppFreeMock
Based on gmock, can mock global function, member function, class static function without changing source code.
该项目好久没维护了,慎用... - 玩转单元测试之cppfreemock
最后期待Google能解决非虚函数和普通非类函数的Mock问题吧。