假/模拟非虚拟 C++ 方法

2021-12-15 00:00:00 mocking testing c++

众所周知,在 C++ 中模拟/伪造非虚拟测试方法是很困难的.例如,googlemock 的cookbook 有两个建议 - 都意味着修改原始源代码以某种方式(模板化和重写为界面).

It known that in C++ mocking/faking nonvirtual methods for testing is hard. For example, cookbook of googlemock has two suggestion - both mean to modify original source code in some way (templating and rewriting as interface).

对于 C++ 代码来说,这似乎是一个非常糟糕的问题.如果您无法修改需要伪造/模拟的原始代码,如何做得最好?复制整个代码/类(整个基类层次结构??)

It appear this is very bad problem for C++ code. How can be done best if you can't modify original code that needs to be faked/mocked? Duplicating whole code/class (with it whole base class hierarchy??)

推荐答案

我遵循了 Link Seam 链接来自 sdg 的答案.我在那里读到了不同类型的接缝,但给我印象最深的是预处理接缝.这让我考虑进一步利用预处理器.事实证明,可以在不实际更改调用代码的情况下模拟任何外部依赖.

I followed the Link Seam link from sdg's answer. There I read about different types of seams, but I was most impressed by Preprocessing Seams. This made me think about exploiting further the preprocessor. It turned out that it is possible to mock any external dependency without actually changing the calling code.

为此,您必须使用替代依赖项定义编译调用源文件.这是一个如何做的例子.

To do this, you have to compile the calling source file with a substitute dependency definition. Here is an example how to do it.

依赖.h

#ifndef DEPENDENCY_H
#define DEPENDENCY_H

class Dependency
{
public:
    //...
    int foo();
    //...
};

#endif // DEPENDENCY_H

调用者.cpp

#include "dependency.h"

int bar(Dependency& dependency)
{
    return dependency.foo() * 2;
}

test.cpp

#include <assert.h>

// block original definition
#define DEPENDENCY_H

// substitute definition
class Dependency
{
public:
    int foo() { return 21; }
};

// include code under test
#include "caller.cpp"

// the test
void test_bar()
{
    Dependency mockDependency;

    int r = bar(mockDependency);

    assert(r == 42);
}

请注意,mock 不需要实现完整的 Dependency,只需实现最小的(由 caller.cpp 使用)以便测试可以编译和执行.通过这种方式,您可以在不更改生产代码的情况下模拟非虚拟、静态、全局函数或几乎任何依赖项.我喜欢这种方法的另一个原因是与测试相关的所有内容都在一个地方.您不必到处调整编译器和链接器配置.

Notice that the mock does not need to implement complete Dependency, just the minimum (used by caller.cpp) so the test can compile and execute. This way you can mock non-virtual, static, global functions or almost any dependency without changing the productive code. Another reason I like this approach is that everything related to the test is in one place. You don't have to tweak compiler and linker configurations here and there.

我已成功地将这种技术应用于具有大量依赖项的现实世界项目.我在 Include mock 中更详细地描述了它.

I have applied this technique successfully on a real world project with big fat dependencies. I have described it in more detail in Include mock.

相关文章