繁体   English   中英

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

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

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

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

和一个 C 二进制文件 ( mybin ),它链接到liblegacy.a

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

function_binary200() {
  function_legacy500();
}

并且mybin使用 Google 测试框架(进行中)进行了部分测试。

技术债务很高,测试mybin和/或liblegacy.a将是一项艰巨的工作。

作为消除这种债务的一个步骤,我想在不影响 rest 的情况下开始对function_binary1进行测试。我的想法是仅模拟它使用的 3 个函数( function_legacy1function_legacy2function_legacy3 )并保持与 lib 的链接,以便我不必拆分 .c/.h 文件以在翻译单元中只包含有趣的部分。

第一种方法可能是制作一个包含 3 个函数的动态共享库,并使用 LD_PRELOAD 在运行时覆盖这些函数。

由于我是 GMock 的新手,也许我们可以直接使用这个框架以更好的方式做到这一点。 是否可以使用 GMock 仅模拟外部库的某些功能以避免在这种情况下进行重构?

注意:这个问题有点相关,但答案对我来说不是很清楚

gmock 可以用于存根 C 函数吗?

如果您正在使用 static 库 ( liblegacy.a ) 和共享库libmock.so (macOS 上的libmock.dylib ),并且如果在 object 文件中定义了任何模拟函数,该文件还包含未模拟的文件,您可能会遇到问题function。

例如,如果您模拟function_legacy1()但在liblegacy.a中它与function_legacy2()在同一个 object 文件中定义,您可能会遇到与function_legacy1()多重定义的链接冲突,或者您可能会遇到错误的代码在运行时执行。

考虑以下设置:

  • legacy.h — 声明遗留函数。
  • legacy_1_2.c — 定义function_legacy1()function_legacy2()
  • legacy_3_4.c — 定义function_legacy3()function_legacy4()
  • mock_1.c — 定义function_legacy1()
  • mock_2.c — 定义function_legacy2()
  • testprog1.c — 定义调用每个遗留函数的main() function。
  • testprog2.ctestprog1.c的副本。

除了名称之外,每个源文件中的每个遗留函数都是相同的:

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

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

代码可以编译运行如下图:

$ 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()
$

如您所见,链接没有问题。 但是, testprog1链接到一个模拟库,该库仅包含function_legacy1()的模拟,并且看不到function_legacy1() () 的模拟版本。 相比之下, testprog2与模拟库链接,该模拟库包含function_legacy1()function_legacy2()的模拟,它确实可以看到这两个模拟函数。

Linux 系统或其他非 macOS 系统上的 YMMV。

因此,如果遗留函数在 static 库中的每个 object 文件中定义了几个,则您的 mocking 可能会或可能不会工作。

听起来您想有条件地将一些函数委托给实际实现( function_legacy1function_legacy2function_legacy3 )并将 rest 委托给模拟。

我认为你应该能够使用这个食谱

对于 mocking 独立函数,例如function_legacy1 ,您可能需要先将它们包装在虚拟 class 中。 是这样的:

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

现在您应该更改代码以使用包装器 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();
}

为测试初始化 legacy_wrapper 的地方:

LegacyInterface *legacy_wrapper = new LegacyMock;

对于生产:

LegacyInterface *legacy_wrapper = new LegacyProduction;

所以你的测试将是:

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

上面的代码仍然可以像以前一样链接到遗留库。

暂无
暂无

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

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