Mocking free function

别说谁变了你拦得住时间么 提交于 2019-12-17 06:46:11

问题


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

I am using VS2005 SP1 for compiling the code.

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.

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


回答1:


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


From GoogleMock's FAQ it says

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.

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.


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:

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.




回答2:


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

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.)

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:

#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.

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.

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?!




回答3:


There are 2 options:

If you insist on using gmock, there's an "extension" for global mocking from 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.

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

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):

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).




回答4:


What has worked for me is

  • to define A* foo() in a separate source file foo.cpp in the main project,
  • not to include foo.cpp in the test project,
  • include a different source file mock-foo.cpp in the test project that provides the mock implementation of A* foo().

For example, pseudocode for the main project file (e.g. .vcxproj or CMakeLists.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.



来源:https://stackoverflow.com/questions/28392277/mocking-free-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!