[英]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.