简体   繁体   English

使用 GMock 仅模拟 C static 库的一些函数

[英]Mock only some functions of a C static library with GMock

Given a C library ( liblegacy.a ) that contains:给定一个包含以下内容的 C 库 ( liblegacy.a ):

function_legacy1()
function_legacy2()
function_legacy3()
...
function_legacy500()

and a C binary ( mybin ) which links against liblegacy.a :和一个 C 二进制文件 ( mybin ),它链接到liblegacy.a

function_binary1() {
  function_legacy1();
  function_legacy2();
  function_legacy3();
}

function_binary200() {
  function_legacy500();
}

and mybin is partially tested with Google Test framework (in progress).并且mybin使用 Google 测试框架(进行中)进行了部分测试。

The technical debt is high and it will be a big work to test mybin and/or liblegacy.a .技术债务很高,测试mybin和/或liblegacy.a将是一项艰巨的工作。

As a step to remove this debt, I like to start implementing a test for function_binary1 without impacting the rest. My idea would be to mock only the 3 functions used by it ( function_legacy1 , function_legacy2 , function_legacy3 ) and keep linking against the lib so that I don't have to split the.c/.h files to have only the interesting part in the translation unit.作为消除这种债务的一个步骤,我想在不影响 rest 的情况下开始对function_binary1进行测试。我的想法是仅模拟它使用的 3 个函数( function_legacy1function_legacy2function_legacy3 )并保持与 lib 的链接,以便我不必拆分 .c/.h 文件以在翻译单元中只包含有趣的部分。

A first approach would probably to make a dynamic shared library that contains the 3 functions, and use LD_PRELOAD to override these ones at runtime.第一种方法可能是制作一个包含 3 个函数的动态共享库,并使用 LD_PRELOAD 在运行时覆盖这些函数。

Since I'm new with GMock, maybe we can do that in a better way directly with this framework.由于我是 GMock 的新手,也许我们可以直接使用这个框架以更好的方式做到这一点。 Is is possible to mock only some functions of an external lib with GMock to avoid refactoring in this case?是否可以使用 GMock 仅模拟外部库的某些功能以避免在这种情况下进行重构?

Note: This question is somehow related to, but answers are not very clear to me注意:这个问题有点相关,但答案对我来说不是很清楚

Can gmock be used for stubbing C functions? gmock 可以用于存根 C 函数吗?

You might run into problems if you are working with a static library ( liblegacy.a ) and a shared library libmock.so ( libmock.dylib on macOS) and if any of the mocked functions is defined in an object file that also contains an unmocked function.如果您正在使用 static 库 ( liblegacy.a ) 和共享库libmock.so (macOS 上的libmock.dylib ),并且如果在 object 文件中定义了任何模拟函数,该文件还包含未模拟的文件,您可能会遇到问题function。

For example, if you mock function_legacy1() but in liblegacy.a it is defined in the same object file as function_legacy2() , you might run into linking conflicts with function_legacy1() being multiply defined, or you might run into the wrong code being executed at runtime.例如,如果您模拟function_legacy1()但在liblegacy.a中它与function_legacy2()在同一个 object 文件中定义,您可能会遇到与function_legacy1()多重定义的链接冲突,或者您可能会遇到错误的代码在运行时执行。

Consider the following setup:考虑以下设置:

  • legacy.h — declares the legacy functions. legacy.h — 声明遗留函数。
  • legacy_1_2.c — defines function_legacy1() and function_legacy2() . legacy_1_2.c — 定义function_legacy1()function_legacy2()
  • legacy_3_4.c — defines function_legacy3() and function_legacy4() . legacy_3_4.c — 定义function_legacy3()function_legacy4()
  • mock_1.c — defines function_legacy1() . mock_1.c — 定义function_legacy1()
  • mock_2.c — defines function_legacy2() . mock_2.c — 定义function_legacy2()
  • testprog1.c — defines a main() function that calls each of the legacy functions. testprog1.c — 定义调用每个遗留函数的main() function。
  • testprog2.c — a copy of testprog1.c . testprog2.ctestprog1.c的副本。

Each of the legacy functions in each source file is the same apart from the name:除了名称之外,每个源文件中的每个遗留函数都是相同的:

void function_legacy1(void)
{
    printf("%s:%d:%s()\n", __FILE__, __LINE__, __func__);
}

The main() program looks like: main()程序如下所示:

#include "legacy.h"
#include <stdio.h>

int main(void)
{
    printf("%s:%d:%s()\n", __FILE__, __LINE__, __func__);
    function_legacy1();
    function_legacy2();
    function_legacy3();
    function_legacy4();
    printf("%s:%d:%s()\n", __FILE__, __LINE__, __func__);
    return 0;
}

The code can be compiled and run as shown below:代码可以编译运行如下图:

$ gcc -shared -o libmock1.dylib mock_1.c
$ gcc -shared -o libmock2.dylib mock_1.c mock_2.c
$ gcc -o testprog1 testprog1.c -L. -lmock1 -llegacy
$ gcc -o testprog2 testprog2.c -L. -lmock2 -llegacy
$ testprog1
testprog1.c:6:main()
legacy_1_2.c:6:function_legacy1()
legacy_1_2.c:11:function_legacy2()
legacy_3_4.c:6:function_legacy3()
legacy_3_4.c:11:function_legacy4()
testprog1.c:11:main()
$ testprog2
testprog2.c:6:main()
mock_1.c:6:function_legacy1()
mock_2.c:6:function_legacy2()
legacy_3_4.c:6:function_legacy3()
legacy_3_4.c:11:function_legacy4()
testprog2.c:11:main()
$

As you can see, there are no problems linking.如您所见,链接没有问题。 However, testprog1 is linked with a mock library that only includes a mock for function_legacy1() , and it doesn't see the mocked version of function_legacy1() .但是, testprog1链接到一个模拟库,该库仅包含function_legacy1()的模拟,并且看不到function_legacy1() () 的模拟版本。 By contrast, testprog2 is linked with a mock library that includes a mock for function_legacy1() and function_legacy2() and it does see both the mocked functions.相比之下, testprog2与模拟库链接,该模拟库包含function_legacy1()function_legacy2()的模拟,它确实可以看到这两个模拟函数。

YMMV on a Linux system, or other non-macOS systems. Linux 系统或其他非 macOS 系统上的 YMMV。

Thus, your mocking may or may not work if the legacy functions are defined several per object file in the static library.因此,如果遗留函数在 static 库中的每个 object 文件中定义了几个,则您的 mocking 可能会或可能不会工作。

Sounds like you want to conditionally delegate some functions to the real implementation ( function_legacy1 , function_legacy2 , function_legacy3 ) and the rest to the mock.听起来您想有条件地将一些函数委托给实际实现( function_legacy1function_legacy2function_legacy3 )并将 rest 委托给模拟。

I think you should be able to use this recipe .我认为你应该能够使用这个食谱

For mocking stand-alone functions such as function_legacy1 , you probably need to wrap them inside a virtual class first.对于 mocking 独立函数,例如function_legacy1 ,您可能需要先将它们包装在虚拟 class 中。 Something like this:是这样的:

class LegacyInterface {
 public:
  ...
  virtual bool function_legacy1(int p) = 0;
  virtual bool function_legacy2(int p) = 0;
  ...
  virtual bool function_legacyn(int p) = 0;
};

// This should be used in production.
class LegacyProduction : public LegacyInterface {
 public:
  
  bool function_legacy1(int p) override {
     return ::function_legacy1(p); // Calling the real function_legacy1
  }
  bool function_legacy2(int p) override {
     return ::function_legacy2(p); // Calling the real function_legacy2
  }
  ...
  bool function_legacyn(int p) override {
     return ::function_legacyn(p); // Calling the real function_legacyn
  }
};

// This should be used in test.
class LegacyMock : public LegacyInterface {
 public:
  // Normal mock method definitions using gMock.
  MOCK_METHOD(bool, function_legacy1, (int p), (override));
  MOCK_METHOD(bool, function_legacy2, (int p), (override));
  ...
  MOCK_METHOD(bool, function_legacyn, (int p), (override));

  // Delegates the default actions of function_legacy500 to the real implementation.
  void Delegate500ToReal() {
    ON_CALL(*this, function_legacy500).WillByDefault([this](int n) {
      return ::function_legacy500(n); // Calling the real function_legacy500
    });
  }
};

Now you should change your code to use the wrapper class:现在您应该更改代码以使用包装器 class:

function_binary1(LegacyInterface *legacy_wrapper) {
  legacy_wrapper->function_legacy1();
  legacy_wrapper->function_legacy2();
  legacy_wrapper->function_legacy3();
}

function_binary200(LegacyInterface *legacy_wrapper) {
  legacy_wrapper->function_legacy500();
}

where you initialize legacy_wrapper for test:为测试初始化 legacy_wrapper 的地方:

LegacyInterface *legacy_wrapper = new LegacyMock;

And for production:对于生产:

LegacyInterface *legacy_wrapper = new LegacyProduction;

So your test would be:所以你的测试将是:

TEST(LegacyTest, function_binary1) {
  LegacyMock legacy_wrapper;
  
  EXPECT_CALL(foo, function_binary1(_));
  EXPECT_CALL(foo, function_binary2(_));
  EXPECT_CALL(foo, function_binary3(_));

  // Assert
  EXPECT_EQ(function_binary1(&legacy_wrapper), ...);
}

TEST(LegacyTest, function_binary200) {
  LegacyMock legacy_wrapper;

  legacy_wrapper.Delegate500ToReal();

  // No action specified, meaning to use the default action, which is calling the real function_legacy500.
  EXPECT_CALL(foo, function_legacy500(_));

  // Assert
  EXPECT_EQ(function_binary200(), ...);
}

The above code can still be linked against the legacy lib as was before.上面的代码仍然可以像以前一样链接到遗留库。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM