嘲弄自由功能

时间:2015-02-08 09:02:36

标签: c++ unit-testing tdd googlemock

我陷入了困境,似乎无法找到解决方案。

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

我有一个全局功能:

A* foo();

我有一个模拟课

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

在消息来源中,它的访问方式如下:foo()->bar()。我无法找到模仿这种行为的方法。我无法改变消息来源,因此谷歌模拟烹饪书中的解决方案是不可能的。

任何有关正确方向的帮助或指示都将受到高度赞赏。 :)

5 个答案:

答案 0 :(得分:15)

如果不更改源代码,或者使用与可执行代码链接的foo()版本的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); } }; ,则不可行。


来自GoogleMock's FAQ它说

  

我的代码调用静态/全局函数。我可以嘲笑它吗?

     

你可以,但你需要做一些改变。

     

通常,如果您发现自己需要模拟静态函数,则表明您的模块耦合得太紧(并且灵活性较低,可重用性较低,可测试性较差等)。你可能最好定义一个小接口并通过该接口调用该函数,然后可以很容易地模拟它。它最初有点工作,但通常会很快收回成本。

     

此Google测试博客post说得非常好。看看吧。

同样来自Cookbook

  

模拟免费功能

     

可以使用Google Mock来模拟自由函数(即C风格函数或静态方法)。您只需要重写代码以使用接口(抽象类)。

     

不是直接调用自由函数(比如OpenFile),而是为它引入一个接口,并有一个调用自由函数的具体子类:

foo()
     

您的代码应与FileInterface通信以打开文件。现在很容易嘲笑这个功能。

     

这可能看起来很麻烦,但实际上你经常会有多个相关的函数可以放在同一个界面中,所以每个函数的语法开销会低得多。

     

如果您担心虚拟功能带来的性能开销,并且性能分析确认您的问题,您可以将其与用于模拟非虚拟方法的配方相结合。


正如您在评论中提到的那样,您实际上提供了自己的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()); // ... } 版本,您可以轻松解决此问题,其中包含另一个模拟类的全局实例:

{{1}}

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

答案 1 :(得分:1)

当然,根据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 - 你需要自己将它添加到测试目标。

可能性相当有限,但你也可以在问题的例子中为返回类型构建一些有用的东西{/ 1}}。

不幸的是,如果不更改代码,这也不是解决方案。 如果确实有必要,可以谷歌进行“链接接缝”。但我的猜测是,这在练习中可能会非常麻烦。在很多/大多数情况下甚至可能根本不可能做到这一点?!

答案 2 :(得分:0)

有2个选项:

如果您坚持使用gmock,则优先级中有一个用于全局模拟的“扩展名”:https://github.com/apriorit/gmock-global

但是,它非常有限-至少我无法在5分钟内弄清楚如何对模拟通话产生副作用。

如果您愿意从gmock切换,那么河马有一种非常整洁的方式来做您想要的事情。

这是一个模拟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作为测试框架,但是我认为河马也可以与Google的Testing框架一起使用(顺便说一句,我建议catch2真的很容易使用)。

答案 3 :(得分:0)

对我有用的是

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

例如,主项目文件的伪代码(例如.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()

简单而甜蜜,但在您的情况下可能奏效或不奏效。

答案 4 :(得分:0)

如果您的自由函数采用 std::function 对象的形式,您可以使用 MockFunction 模拟它。见this answer