无模拟功能

Mocking free function

本文关键字:功能 模拟      更新时间:2023-10-16

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

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

我有一个全局功能:

A* foo();

我有一个模拟课堂

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

在源中,它的访问方式如下:foo()->bar()。我找不到嘲笑这种行为的方法。我无法更改来源,所以谷歌模拟烹饪书中的解决方案是毫无疑问的。

如有任何帮助或指示,我们将不胜感激。:)

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


从GoogleMock的常见问题解答中可以看出

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

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

一般来说,如果你发现自己需要模拟一个静态函数,这表明你的模块耦合过于紧密(灵活性较差、可重用性较差、可测试性较差等)。您最好定义一个小接口,并通过该接口调用函数,这样就可以很容易地进行模拟。最初这是一项艰巨的工作,但通常很快就会得到回报。

这篇谷歌测试博客文章说得很好。看看吧。

同样来自食谱

无模拟函数

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

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

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);
  }
};

您的代码应该与FileInterface对话以打开文件。现在很容易模拟出这个函数。

这看起来可能很麻烦,但在实践中,您经常有多个相关的函数可以放在同一个接口中,因此每个函数的语法开销会低得多。

如果您关心虚拟函数所带来的性能开销,并且评测证实了您的担忧,那么您可以将其与模拟非虚拟方法的配方结合起来。


正如您在评论中提到的那样,您实际上提供了自己版本的foo(),您可以通过使用另一个模拟类的全局实例来轻松解决这个问题:

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());
    // ...
}

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

有两个选项:

如果你坚持使用gmock,那么apriorit为全局嘲讽提供了一个"扩展":https://github.com/apriorit/gmock-global

不过,这是相当有限的——或者至少我无法在5分钟内弄清楚如何对一个被嘲笑的电话产生副作用。

如果你愿意从gmock切换,那么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也可以与谷歌的测试框架配合使用(顺便说一句,我推荐catch2非常容易使用)。

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

但我想添加一个临时的快速&肮脏的做法。它应该适用于您希望尽可能快速、无入侵地测试遗留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/GLock,您需要自己将其添加到测试目标中。

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

不幸的是,如果不更改代码,这也不是一个解决方案。如果真的有必要,你可以在谷歌上搜索"链接接缝"。但我的猜测是,这在实践中可能会很麻烦。在许多/大多数情况下,这甚至可能根本不可能?!

如果您的自由函数是std::function对象的形式,则可以使用MockFunction对其进行模拟。请参阅此答案

对我有效的是

  • 为了在主项目中的单独源文件CCD_ 10中定义CCD_
  • 而不是将CCD_ 11包括在测试项目中
  • 在提供CCD_ 13的模拟实现的测试项目中包括不同的源文件CCD_

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

简单而甜蜜,但在你的情况下可能有效,也可能无效。