[英]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_legacy1
、 function_legacy2
、 function_legacy3
)並保持與 lib 的鏈接,以便我不必拆分 .c/.h 文件以在翻譯單元中只包含有趣的部分。
第一種方法可能是制作一個包含 3 個函數的動態共享庫,並使用 LD_PRELOAD 在運行時覆蓋這些函數。
由於我是 GMock 的新手,也許我們可以直接使用這個框架以更好的方式做到這一點。 是否可以使用 GMock 僅模擬外部庫的某些功能以避免在這種情況下進行重構?
注意:這個問題有點相關,但答案對我來說不是很清楚
如果您正在使用 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.c
— testprog1.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_legacy1
、 function_legacy2
、 function_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.