【问题标题】:C++ ld linker --wrap option does not work for internal function callsC++ ld 链接器 --wrap 选项不适用于内部函数调用
【发布时间】:2017-11-11 21:28:29
【问题描述】:

我正在尝试为不使用 OO 的 C++ 库实现一些单元测试(所有函数都在命名空间级别声明)

为此,我正在尝试创建一个模拟(模拟)某些功能的测试二进制文件。

对于我直接调用的函数,我已经实现了上述功能,但是我无法替换库函数所做的调用。下面的例子解释了这一点:

生产代码

假设这是生产代码,使用真实函数而不是模拟函数的代码:

CameraHandler.H

namespace Cam {
    int myFunc();
    int myFunc2();
}

CameraHandler.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.
}

CameraHandlerMock.cpp

#include "CameraHandlerMock.h"

int __wrap__ZN3Cam6myFuncEv(){
    // As you can see, the mocked function returns 999 instead of 1.
    return 999;
}

UnitTestsMain.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;
}

Makefile

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

有什么方法可以包装我要测试的库内部调用的函数

【问题讨论】:

    标签: c++ unit-testing linker mocking ld


    【解决方案1】:

    让我们先去除一点绒毛。在UnitTestsMain.cpp, 声明:

    extern "C" int _ZN3Cam6myFuncEv();
    

    是多余的。它只是指示引用该函数的 C++ 编译器 mangled 名称为 _ZN3Cam6myFuncEv 的原型中的 该名称的外部定义函数。这是完全相同的信息, 只是表达方式不同,编译器已经从:

    namespace Cam {
        int myFunc();
        ...
    }
    

    当它#include-ed CameraHandler.h,因为_ZN3Cam6myFuncEv() 是被破坏的 Cam::myFunc 的形式。 Cam::myFuncextern "C" 重新声明是无害的 但对编译或链接没有任何贡献。

    关于主要问题:为什么你的模拟 在UnitTestsMain.cpp 中调用int __wrap__ZN3Cam6myFuncEv() 而不是int Cam::myFunc

    int main(){
        std::cout << Cam::myFunc() << std::endl;
        std::cout << Cam::myFunc2() << std::endl;
        return 0;
    } 
    

    如你所愿;但是您的模拟CameraHandler.cpp 中调用int Cam::myFunc

    int Cam::myFunc2(){
        return Cam::myFunc() + 11;
    }
    

    答案就在the documentation of the --wrap linker option

    --wrap=符号

    对符号使用包装函数。对符号的任何未定义引用将是 解析为 __wrap_symbol。对 __real_symbol 的任何未定义引用都将是 解析为符号。

    也许你读了它并没有理解未定义引用的意义。

    这意味着当--wrap=symbol 生效时,链接器会应用它 到包含 undefinedsymbol 的引用的目标文件,它将替换它们 引用__wrap_symbolundefined 引用__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 mocked 被编译成一个不包含(真实)定义的目标文件 的Cam::myFunc。显而易见的方法是在自己的定义 Cam::myFunc 源文件。这是固定的示例:

    CameraHandler.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;
    }
    

    CameraHandlerMock.cpp

    #include "CameraHandlerMock.h"
    
    int __wrap__ZN3Cam6myFuncEv() {
        return 999;
    }
    

    UnitTestsMain.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
    

    (在此示例 makefile 中根本不考虑您的生产构建)

    有了这个,测试构建运行如下:

    $ 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
    

    如果你重写,你可以稍微简化源文件和makefile CameraHandlerMock.cpp 刚刚:

    extern "C" {
    
    int __wrap__ZN3Cam6myFuncEv() {
        return 999;
    }
    
    }
    

    那么你根本不需要mock头文件CameraHandlerMock.h

    如果你有很多函数需要以这种低级方式模拟,它 在自己的源文件中定义每一个可能会很乏味。你可能知道 有更高级别的、框架支持的模拟选项,例如googlemock, 具有丰富的模拟功能并且不会带来这种乏味。然而,公平地说,他们可能 用更复杂的乏味代替它。

    【讨论】:

    • 是的,我已经在使用 googlemock 进行 C++ 单元测试。我写这篇文章是因为我想要一种模拟命名空间级函数的方法,据我所知,你不能直接用 google mock 模拟,因为你需要注入一个接口。
    • 这一点我没有考虑过支持这种方式。值得回答 3 个月大的 Q :)
    猜你喜欢
    • 2011-07-05
    • 2016-02-19
    • 1970-01-01
    • 2010-10-22
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多