C++ 模拟免费功能

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28392277/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 20:58:18  来源:igfitidea点击:

Mocking free function

c++unit-testingtddgooglemock

提问by Muhammad Hassan

I am stuck in a problem and can't seem to find the solution.

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

I am using VS2005 SP1 for compiling the code.

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

I have a global function:

我有一个全局函数:

A* foo();

I have a mock class

我有一个模拟课

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

In the sources, it is accessed like this: foo()->bar(). I cannot find a way to mock this behavior. And I cannot change the sources, so the solution in google mock cook book is out of question.

在该人士透露,这是访问这样的:foo()->bar()。我找不到嘲笑这种行为的方法。而且我无法更改来源,因此 google mock Cook book 中的解决方案是不可能的。

Any help or pointers in the right direction will be highly appreciated. :)

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

回答by π?ντα ?ε?

No it's not possible, without changing the sources, or bringing your own version of foo()that is linked with the executable code.

不,这是不可能的,不改变源代码,或者带上你自己的foo()与可执行代码链接的版本。



From GoogleMock's FAQit says

来自GoogleMock 的常见问题解答它说

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 postsays it excellently. Check it out.

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

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

一般来说,如果您发现自己需要模拟一个静态函数,则表明您的模块耦合太紧(灵活性较差、可重用性较差、可测试性较差等)。您可能最好定义一个小接口并通过该接口调用该函数,然后可以轻松模拟。最初需要一些工作,但通常很快就会收回成本。

这篇 Google 测试博客文章说得很好。一探究竟。

Also from the 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.

模拟免费功能

可以使用 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 对话以打开文件。现在很容易模拟这个函数。

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

如果您担心虚函数产生的性能开销,并且分析证实了您的担忧,您可以将其与模拟非虚方法的秘诀结合起来。



As you mentioned in your comment that you actually provide your own version of foo(), you can easily solve this having a global instance of another mock class:

正如您在评论中提到的,您实际上提供了自己的 版本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());
    // ...
}

Don't forget to clear all call expectations, after each test case run.

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

回答by yau

Of course, the answer explaining the solution according to GTest/GMock's documentation couldn't be much more correct.

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

But I would like to add a temporary quick&dirty approach. It should be applicable to cases where you want to get legacy C/C++ code under test as quickly and as non-invasively as possible. (Just to proceed with fixes, refactoring and more proper testing as soon as possible after.)

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

So, to mock a free function void foo(int)appearing in some code to be tested, within the source file you just make the following adaptions:

因此,要模拟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

The macro TESTING, indicating that the code runs under test, doesn't come with GTest/GMock - you need to add it to test targets by yourself.

TESTING,表示代码在测试中运行,不附带 GTest/GMock - 您需要自己将其添加到测试目标中。

The possibilities are rather limited, but you might also be able to construct something useful for return types as A*in the question's example.

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

Unfortunately, also this isn't a solution without changing the code. If that is really necessary, you could Google for 'link seams'. But my guess is that this could be quite a hassle in practice. And it even might not be possible at all in many/most cases?!

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

回答by Mr.WorshipMe

There are 2 options:

有2个选项:

If you insist on using gmock, there's an "extension" for global mocking from apriorit: https://github.com/apriorit/gmock-global

如果你坚持使用 gmock,apriorit 有一个全局模拟的“扩展”:https: //github.com/apriorit/gmock-global

It's rather limited, though - or at least I couldn't figure out in 5 minutes how to have side effects on a mocked call.

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

If you're willing to switch from gmock, then hippomocks has a very neat way of doing what you want.

如果你愿意从 gmock 切换,那么 hippomocks 有一种非常简洁的方式来做你想做的事。

Here's an example for mocking fopen, fclose and fgets for testing a member function which reads from a file using cstdio (streams are very inefficient):

这是一个模拟 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(""));
}

Where the function under test looks like this:

被测函数如下所示:

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

I'm using catch2 as the testing framework here, but I think hippomocks would work with Google's Testing framework as well (I recommend catch2, by the way, really easy to work with).

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

回答by mrts

What has worked for me is

对我有用的是

  • to define A* foo()in a separate source file foo.cppin the main project,
  • notto include foo.cppin the test project,
  • include a different source file mock-foo.cppin the test project that provides the mock implementation of A* foo().
  • 在主项目A* foo()的单独源文件foo.cpp中定义,
  • 包括foo.cpp在测试项目中,
  • mock-foo.cpp在测试项目中包含一个不同的源文件,该文件提供A* foo().

For example, pseudocode for the main project file (e.g. .vcxprojor CMakeLists.txt):

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

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

and the test project file:

和测试项目文件:

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

Simple and sweet, but may or may not work in your case.

简单而甜蜜,但可能适用于您的情况,也可能不起作用。