在没有虚拟方法的情况下模拟 class

Mocking class with no virtual methods

我刚刚开始使用 google mock 对生产代码进行单元测试。我想模拟的 classes 里面没有虚方法。通过阅读一些关于 google mock 的内容,是否不可能像这样模拟 class?请记住,我不想对源代码进行任何更改。

class Test
{
    Test(void);
    virtual ~Test() {}
    Add();
};

int Test::Add()
{
    return 1;
}

class MockTest : public Test
{
public:
    MOCK_METHOD0(Add,int(void));
};

可能有办法,但不如覆盖虚函数的简单方法好。

首先,如果该函数不是虚拟的并且是内联的,那么您可能就不走运了。当编译器看到对 obj.Add()ptr->Add() 的调用时,该函数不是虚函数意味着它不需要担心 Test::Add() 以外的某些函数可能需要虚函数的可能性实际调用的那个。所以它很可能直接内联 Add 定义的代码,在这种情况下替换它几乎是不可能的,或者它把 Test::Add() 的弱 linked 副本放入与调用它的函数相同的目标文件。在第二种情况下,您可以使用 linker 技巧替换它,具体取决于您使用的平台 - 直到稍后编译器切换到最终决定内联它。

如果只是 class Test 你不想修改,但是你可以更改使用 Test 作为依赖的代码并进行测试通过单元测试,你可以做模板依赖注入。但问题听起来你可能也不想修改该代码。

现在假设该函数不是内联的,并且在某个文件 "Test.cpp" 中定义,并且 class 是多态的(由于虚拟析构函数,它在示例中)您可以替换该文件中的所有定义,使它们表现得好像它们是虚拟的,即使它们不是:

  1. 像往常一样编写 Google Mock class,使用您希望能够检测到的函数进行模拟。

    #include "Test.hpp"
    #include <gmock/gmock.h>
    
    class MockTest : public Test
    {
    public:
        MOCK_METHOD0(Add, int());
        MOCK_CONST_METHOD0(Print, void());
    };
    

(我在示例中添加了一个 const 方法,以展示如何处理它们。)

  1. 在同一个单元测试代码中,像这样为模拟函数编写定义。

    int Test::Add()
    {
        if (auto* mock = dynamic_cast<MockTest*>(this))
            return mock->Add();
        // Next comes what to do if the code ever calls Add on
        // a Test which is not actually a MockTest. This could
        // be a stub implementation, an actual implementation, or
        // could intentionally terminate, throw, or note a gtest error.
        ADD_FAILURE() << "Test is not a MockTest";
        return 0;
    }
    
    void Test::Print() const
    {
        if (auto* mock = dynamic_cast<const MockTest*>(this)) {
            mock->Print();
            return;
        }
        ADD_FAILURE() << "Test is not a MockTest";
    }
    

您可能还需要至少为 Test.cpp 文件中的其他定义编写存根,以使 linker 满意。这并不一定意味着它们实际上会被调用或以其他方式使用。

  1. 当link进行单元测试时,确保没有提供真正的Test.cpp文件。如果它通常是库的一部分,您可能需要在命令行中列出该库中的其他文件。这可能会导致依赖顺序问题 and/or 循环依赖问题。 GNU 的 linker 有“-Wl,--start-group ... -Wl,--end-group”通过重复尝试一些对象 and/or 库来处理循环 link 问题一个循环,直到尽可能多地被解决;我不确定其他系统。