簡體   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