我正在尝试为不使用 OO 的C++库实现一些单元测试(所有函数都在命名空间级别声明)
为此,我正在尝试创建一个模拟(模拟)某些函数的测试二进制文件。
我已经为我直接调用的函数实现了上述目标,但是我无法替换库函数所做的调用。下面的示例对此进行了解释:
生产代码
假设这是生产代码,使用真实函数而不是模拟函数的代码:
相机处理程序.H
namespace Cam {
int myFunc();
int myFunc2();
}
相机处理程序.cpp
#include "CameraHandler.h"
using namespace Cam;
int Cam::myFunc() {
// Imagine this is the function I want to simulate with a mock
// Its mangled name is _ZN3Cam6myFuncEv
return 1;
}
int Cam::myFunc2(){
return Cam::myFunc() + 11;
}
测试代码
这是单元测试的代码。正如你在 Makefile 中看到的,它生成了一个名为testsMain的二进制文件。
CameraHandlerMock.h
extern "C" {
int __wrap__ZN3Cam6myFuncEv(); // mangled name of Cam::myFunc(), with the __wrap_ prefix.
}
相机处理程序模拟.cpp
#include "CameraHandlerMock.h"
int __wrap__ZN3Cam6myFuncEv(){
// As you can see, the mocked function returns 999 instead of 1.
return 999;
}
单元测试主.cpp
#include <iostream>
#include <typeinfo>
#include "CameraHandler.h"
#include "CameraHandlerMock.h"
extern "C" int _ZN3Cam6myFuncEv();
int main(){
std::cout << Cam::myFunc() << std::endl;
std::cout << Cam::myFunc2() << std::endl;
return 0;
}
生成文件
WRAP=-Wl,--wrap,_ZN3Cam6myFuncEv
all: production unitTests
production: // does not matter for this example
g++ main.cpp CameraHandler.cpp -o main
unitTests:
g++ ${WRAP} UnitTestsMain.cpp CameraHandlerMock.cpp CameraHandler.cpp -o testsMain
问题所在
如果我执行testsMain
程序,我会得到以下结果:
999 // call to Cam::myFunc()
12 // Cam::myFunc2(), which is Cam::myFunc() + 11.
考虑到Cam::myFunc2()
调用Cam::myFunc1()
,我用__wrap__ZN3Cam6myFuncEv
代替了它,我期望的是调用Cam::myFunc2()
的结果是999 + 11 = 1010
。尽管如此,Cam::myFunc2()
仍然调用非包装Cam::myFunc1()
,所以结果是12
。
有什么方法可以包装我要测试的库在内部调用的函数?
让我们先皮毛一点绒毛。在UnitTestsMain.cpp
, 宣言:
extern "C" int _ZN3Cam6myFuncEv();
是多余的。它只是指示引用函数的C++编译器 该原型的名称为_ZN3Cam6myFuncEv
引用 该名称的外部定义的函数。这是完全相同的信息, 只是表达方式不同,编译器已经从
namespace Cam {
int myFunc();
...
}
当它#include
-edCameraHandler.h
时,因为_ZN3Cam6myFuncEv()
是被破坏的Cam::myFunc
形式 .extern "C"
重新声明Cam::myFunc
是无害的 但对编译或链接没有任何贡献。
关于主要问题:为什么你的模拟int __wrap__ZN3Cam6myFuncEv()
被调用而不是int Cam::myFunc
UnitTestsMain.cpp
:
int main(){
std::cout << Cam::myFunc() << std::endl;
std::cout << Cam::myFunc2() << std::endl;
return 0;
}
如你所愿; 但是你的模拟不需要int Cam::myFunc
CameraHandler.cpp
:
int Cam::myFunc2(){
return Cam::myFunc() + 11;
}
答案就在--wrap
链接器选项的文档中:
-换行=符号
对符号使用包装函数。任何未定义的符号引用都将是 决心__wrap_symbol。对__real_symbol的任何未定义的引用都将是 解析为符号。
也许你读了它,并没有意识到未定义引用的意义。
这意味着当--wrap=symbol
生效时,链接器会应用它 到一个包含对symbol
的未定义引用的对象文件,它将替换它们 引用__wrap_symbol
,未定义引用__real_symbol
, 在该对象文件中,将替换为symbol
。
现在在UnitTestsMain.o
,从UnitTestsMain.cpp
编译,对这两个Cam::myFunc()
的引用Cam::myFunc2()
未定义。这些函数都在CameraHandler.cpp
中定义,CameraHandler.o
编译。
因此在UnitTestsMain.o
的联动中,--wrap ZN3Cam6myFuncEv
将生效和 将对Cam::myFunc
( =ZN3Cam6myFuncEv
) 的调用替换为对__wrap_ZN3Cam6myFuncEv
的调用。 对Cam::myFunc2()
( =ZN3Cam7myFunc2Ev
) 的调用未包装且不受影响:它将 已解析为CameraHandler.o
中的定义
但是在CameraHandler.o
的联动中,两个函数都被定义了,所以--wrap
有 没有效果。当Cam::myFunc2()
调用 Cam::myFunc() 时,它调用的是ZN3Cam6myFuncEv
,而不是__wrap_ZN3Cam6myFuncEv
.
这就解释了为什么程序输出:
999
12
而不是:
999
1010
你能让你的嘲笑工作如预期的那样吗?
是的。你只需要确保每一个电话Cam::myFunc
你想成为 模拟被编译为不包含(真实)定义的对象文件 的Cam::myFunc
.显而易见的方法是定义Cam::myFunc
自己的 源文件。这是您的示例已修复:
相机处理程序.h
#ifndef CAMERAHANDLER_H
#define CAMERAHANDLER_H
namespace Cam {
int myFunc();
int myFunc2();
}
#endif
CameraHandlerMock.h
#ifndef CAMERAHANDLERMOCK_H
#define CAMERAHANDLERMOCK_H
extern "C" {
int __wrap__ZN3Cam6myFuncEv();
}
#endif
CameraHandler_myFunc.cpp
#include "CameraHandler.h"
using namespace Cam;
int Cam::myFunc() {
return 1;
}
CameraHandler_myFunc2.cpp
#include "CameraHandler.h"
using namespace Cam;
int Cam::myFunc2(){
return Cam::myFunc() + 11;
}
相机处理程序模拟.cpp
#include "CameraHandlerMock.h"
int __wrap__ZN3Cam6myFuncEv() {
return 999;
}
单元测试主.cpp
#include <iostream>
#include "CameraHandler.h"
#include "CameraHandlerMock.h"
int main(){
std::cout << Cam::myFunc() << std::endl;
std::cout << Cam::myFunc2() << std::endl;
return 0;
}
生成文件
SRCS := UnitTestsMain.cpp CameraHandler_myFunc.cpp
CameraHandler_myFunc2.cpp CameraHandlerMock.cpp
OBJS := $(SRCS:.cpp=.o)
LDFLAGS := -Wl,--wrap,_ZN3Cam6myFuncEv
.PHONY: unitTests clean
unitTests: testsMain
testsMain: $(OBJS)
$(CXX) $(LDFLAGS) -o $@ $^
UnitTestsMain: CameraHandler.h CameraHandlerMock.h
CameraHandler_Func.o CameraHandler_Func2.o: CameraHandler.h
CameraHandlerMock.o: CameraHandlerMock.h
clean:
rm -f $(OBJS) testsMain
(在此示例生成文件中根本不考虑您的生产版本)
这样,测试版本将像以下方式运行:
$ make
g++ -c -o UnitTestsMain.o UnitTestsMain.cpp
g++ -c -o CameraHandler_myFunc.o CameraHandler_myFunc.cpp
g++ -c -o CameraHandler_myFunc2.o CameraHandler_myFunc2.cpp
g++ -c -o CameraHandlerMock.o CameraHandlerMock.cpp
g++ -Wl,--wrap,_ZN3Cam6myFuncEv -o testsMain UnitTestsMain.o
CameraHandler_myFunc.o CameraHandler_myFunc2.o CameraHandlerMock.o
testsMain
会执行您的期望:
$ ./testsMain
999
1010
如果重写,则可以稍微简化源文件和生成文件CameraHandlerMock.cpp
只是:
extern "C" {
int __wrap__ZN3Cam6myFuncEv() {
return 999;
}
}
那么你根本不需要模拟头文件CameraHandlerMock.h
。
如果你有很多函数需要以这种低级的方式进行模拟,它 在自己的源文件中定义每个可能会变得乏味。你可能知道 有更高级别的、框架支持的模拟选项,例如 GoogleMock, 具有丰富的嘲笑能力,不需要这种乏味。然而,公平地说,他们可能会 用更复杂的乏味取而代之。