模拟免费功能

Mocking free function

我遇到了一个问题,似乎找不到解决方案。

我正在使用 VS2005 SP1 编译代码。

我有一个全局函数:

A* foo();

我有一个模拟class

class MockA : public A {
public:
    MOCK_METHOD0 (bar, bool());
    ...
};

在源代码中,它是这样访问的:foo()->bar()。我找不到一种方法来模拟这种行为。而且我无法更改来源,因此 google mock cook book 中的解决方案是不可能的。

任何正确方向的帮助或指示将不胜感激。 :)

不,这是不可能的,如果不更改源代码,或者带来与可执行代码链接的您自己的 foo() 版本。


来自GoogleMock's FAQ 它说

My code calls a static/global function. Can I mock it?

You can, but you need to make some changes.

In general, if you find yourself needing to mock a static function, it's a sign that your modules are too tightly coupled (and less flexible, less reusable, less testable, etc). You are probably better off defining a small interface and call the function through that interface, which then can be easily mocked. It's a bit of work initially, but usually pays for itself quickly.

This Google Testing Blog post says it excellently. Check it out.

也来自Cookbook

Mocking Free Functions

It's possible to use Google Mock to mock a free function (i.e. a C-style function or a static method). You just need to rewrite your code to use an interface (abstract class).

Instead of calling a free function (say, OpenFile) directly, introduce an interface for it and have a concrete subclass that calls the free function:

class FileInterface {
 public:
  ...
  virtual bool Open(const char* path, const char* mode) = 0;
};
class File : public FileInterface {
 public:
  ...
  virtual bool Open(const char* path, const char* mode) {
    return OpenFile(path, mode);
  }
};

Your code should talk to FileInterface to open a file. Now it's easy to mock out the function.

This may seem much hassle, but in practice you often have multiple related functions that you can put in the same interface, so the per-function syntactic overhead will be much lower.

If you are concerned about the performance overhead incurred by virtual functions, and profiling confirms your concern, you can combine this with the recipe for mocking non-virtual methods.


正如您在评论中提到的,您实际上提供了自己的 foo() 版本,您可以使用另一个模拟 class:

的全局实例轻松解决此问题
struct IFoo {
    virtual A* foo() = 0;
    virtual ~IFoo() {}
};

struct FooMock : public IFoo {
     FooMock() {}
     virtual ~FooMock() {}
     MOCK_METHOD0(foo, A*());
};

FooMock fooMock;

// Your foo() implementation
A* foo() {
    return fooMock.foo();
}

TEST(...) {
    EXPECT_CALL(fooMock,foo())
        .Times(1)
        .WillOnceReturn(new MockA());
    // ...
}

不要忘记在每个测试用例后清除所有调用期望 运行。

当然,根据 GTest/GMock 的文档解释解决方案的答案再正确不过了。

但我想添加一个临时的快速和肮脏的方法。它应该适用于您希望尽可能快速且尽可能无创地获取遗留 C/C++ 待测代码的情况。 (只是为了尽快进行修复、重构和更适当的测试。)

因此,要模拟一个自由函数 void foo(int) 出现在一些要测试的代码中,您只需在源文件中进行以下修改:

#if TESTING
#define foo(param) // to nothing, so calls to that disappear
#endif

// ... code that calls foo stays untouched and could be tested

TESTING,表示代码在测试下运行,GTest/GMock没有自带,需要自己添加到测试目标中。

可能性相当有限,但您也可以构建对 return 类型有用的东西,如问题示例中的 A*

不幸的是,这也不是不更改代码的解决方案。 如果确实有必要,您可以 Google 代替 'link seams'。但我的猜测是,这在实践中可能会很麻烦。在 many/most 的情况下甚至可能根本不可能?!

有2个选项:

如果您坚持使用 gmock,则有一个 "extension" 用于来自 apriorit 的全局模拟:https://github.com/apriorit/gmock-global

虽然它相当有限 - 或者至少我无法在 5 分钟内弄清楚如何对模拟调用产生副作用。

如果您愿意从 gmock 切换到 hippomocks,那么 hippomocks 有一种非常简洁的方法可以满足您的需求。

这是一个模拟 fopen、fclose 和 fgets 的示例,用于测试使用 cstdio 从文件中读取的成员函数(流非常低效):

TEST_CASE("Multi entry") {
    std::vector<std::string> files{"Hello.mp3", "World.mp3"};
    size_t entry_idx = 0;
    MockRepository mocks;
    mocks.OnCallFunc(fopen).Return(reinterpret_cast<FILE *>(1));
    mocks.OnCallFunc(fgets).Do(
        [&](char * buf, int n, FILE * f)->char *{ 
            if (entry_idx < files.size())
            {
                strcpy(buf, files[entry_idx++].c_str());
                return buf;
            }
            else
                return 0;
            }
        );
    mocks.OnCallFunc(fclose).Return(0);

    FileExplorer file_explorer;
    for (const auto &entry: files)
        REQUIRE_THAT(file_explorer.next_file_name(), Equals(entry.c_str()));
    REQUIRE_THAT(file_explorer.next_file_name(), Equals(""));
}

被测函数如下所示:

string FileExplorer::next_file_name() {
    char entry[255];
    if (fgets((char *)entry, 255, _sorted_entries_in_dir) == NULL)
        return string();
    _current_idx++;
    if (_current_idx == _line_offsets.size())
        _line_offsets.push_back(static_cast<unsigned>(char_traits<char>::length(entry)) + _line_offsets.back());
    return string(entry);
} 

我在这里使用 catch2 作为测试框架,但我认为 hippomocks 也可以与 Google 的测试框架一起使用(顺便说一句,我推荐 catch2,它真的很容易使用)。

对我有用的是

  • 在主项目的单独源文件foo.cpp中定义A* foo()
  • 在测试项目中包含foo.cpp
  • 在提供 A* foo() 的模拟实现的测试项目中包含一个不同的源文件 mock-foo.cpp

例如,主项目文件的伪代码(例如 .vcxprojCMakeLists.txt):

include src/foo.hpp # declare A* foo()
include src/foo.cpp # define A* foo()

和测试项目文件:

include src/foo.hpp
include test/mock-foo.cpp # define mocked A* foo()

简单而贴心,但可能适用于您的情况,也可能不适用。

如果您的自由函数是 std::function 对象的形式,您可以使用 MockFunction. See

模拟它