我有一个类调用getaddrinfo进行DNS查找。在测试期间,我想模拟涉及此系统调用的各种错误情况。模拟这样的系统调用的推荐方法是什么?我正在使用Boost.Test进行单元测试。
答案 0 :(得分:27)
在这种情况下,您不需要模拟getaddrinfo
,而是需要在不依赖其功能的情况下进行测试。帕特里克和诺亚都有很好的分数,但你至少还有两个选择:
由于您已经在类中拥有了对象,因此可以子类化以进行测试。例如,假设以下是您的实际类:
class DnsClass {
int lookup(...);
};
int DnsClass::lookup(...) {
return getaddrinfo(...);
}
然后,为了测试,你会像这样继承:
class FailingDnsClass {
int lookup(...) { return 42; }
};
您现在可以使用FailingDnsClass
子类生成错误,但仍会在发生错误情况时验证一切正常。在这种情况下,依赖注入通常是你的朋友。
注意:这与Patrick的答案非常相似,但如果您尚未设置依赖注入,则不会(希望)涉及更改生产代码。
在C ++中,您还拥有Michael Feathers在Working Effectively with Legacy Code中描述的链接时间接缝。
基本思想是利用链接器和构建系统。编译单元测试时,请链接到您自己的getaddrinfo
版本,该版本将优先于系统版本。例如:
TEST.CPP:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <iostream>
int main(void)
{
int retval = getaddrinfo(NULL, NULL, NULL, NULL);
std::cout << "RV:" << retval << std::endl;
return retval;
}
lib.cpp:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res
)
{
return 42;
}
然后进行测试:
$ g++ test.cpp lib.cpp -o test
$ ./test
RV:42
答案 1 :(得分:13)
查找“依赖注入”的模式。
依赖注入的工作原理如下:代码不是直接在代码中调用getaddrinfo,而是使用具有虚拟方法“getaddrinfo”的接口。
在实际代码中,调用者传递接口的实现,该接口将接口的虚拟方法“getaddrinfo”映射到real :: getaddrinfo函数。
在单元测试中,调用者传递一个可以模拟失败,测试错误条件的实现,......简短:模拟你想要模拟的任何内容。
编辑:阅读Michael Feathers的"Working effectively with legacy code"以获取更多提示。
答案 2 :(得分:10)
3个选项
<强> 1 即可。使用gnu链接器的模拟功能--wrap
选项。我从来没有用它来测试生产代码,因为直到我们的开发团队已经提交到方法3之前我才发现它。我希望我们能早点找到它
ld --wrap=getaddrinfo /*the rest of the link line*/
or
g++ -Wl,--wrap=getaddrinfo /* the rest of the build line*/
// this in the unit tests.
bool g_getaddrinfo_use_real = true;
int g_getaddrinfo_ret = -1;
int g_getaddrinfo_errno = something;
int __wrap_getaddrinfo( const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res )
{
if( g_getaddrinfo_use_real )
return __real_getaddrinfo(node,service,hints,res);
errno = g_getaddrinfo_errno;
return g_getaddrinfo_ret;
}
<强> 2 即可。定义您自己的getaddrinfo并将其静态链接到您的测试应用程序。这只有在libc动态链接时才有效,99%的情况下都是如此。此方法还有一个缺点,即在单元测试应用程序中永久禁用真正的getaddrinfo,但实现起来非常简单。
int g_getadderinfo_ret = -1;
int g_getaddrinfo_errno = something;
int getaddrinfo( const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res )
{
errno = g_getaddrinfo_errno
return g_getaddrinfo_ret;
}
第3 即可。使用相同的名称定义您自己的中间函数。然后,如果您愿意,您仍然可以拨打原件。使用某些宏来帮助重复更容易。如果你想模拟可变参数函数(printf
,open
等),你还必须使用gnu扩展。
typedef (*getaddrinfo_func_type)( const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res );
getaddrinfo_func_type g_getaddrinfo_func;
int getaddrinfo( const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res )
{
return g_getaddrinfo_func( node, service, hints, res )
}
int g_mock_getadderinfo_ret = -1;
int g_mock_getaddrinfo_errno = something;
int mock_getaddrinfo( const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res )
{
errno = g_mock_getaddrinfo_errno;
return g_mock_getaddrinfo_ret;
}
// use the original
g_getaddrinfo_func = dlsym(RTDL_NEXT, "getaddrinfo");
// use the mock version
g_getaddrinfo_func = &mock_getaddrinfo;
答案 3 :(得分:2)
在ELF系统上,您可以使用elf_hook临时替换动态链接的符号。
它允许您创建任意函数并用它替换动态链接函数。
dlopen
)elf_hook具有以下签名:
void* elf_hook(char const* library_filename,
void const* library_address,
char const* function_name,
void const* substitution_address);
你会用这样的:
int hooked_getaddrinfo(const char* node,
const char* service,
const struct addrinfo* hints,
struct addrinfo** res)
{
return 42;
}
const char* lib_path = "path/to/library/under/test.so";
void* lib_handle = dlopen(lib_path, RTLD_LAZY);
elf_hook(lib_path, LIBRARY_ADDRESS_BY_HANDLE(lib_handle), "getaddrinfo", hooked_getaddrinfo);
现在,在受测试的库中对getaddrinfo
的任何来电都会调用hooked_getaddrinfo
。
elf_hook作者Anthony Shoumikhin的综合文章是here。
答案 4 :(得分:1)
虽然技术上可行,但我认为不可行。您必须能够替换该功能的实现,并且您可能无法并仍然链接到系统上的标准库。
你应该做的是打电话给中间人。然后你可以在测试期间模拟中介,然后转发到生产中的实际功能。您甚至可以考虑创建一个与此函数交互的类以及其他类似函数,并为您的程序提供更通用的接口。这个类实际上不会做任何事情,只能在大多数时间转发,但在测试期间它可以被有效地模拟,你可以测试任何用途。
事情是要保留这样的东西,无法测试的东西,包含在如此微不足道的东西中,它实际上不需要测试,然后模拟包装器来测试更复杂的交互。 KISS在这里特别重要。
答案 5 :(得分:0)
免责声明:我写过ELFspy。
您可以在单元测试中使用ELFspy,通过提供具有相同签名的函数,将对库中的getaddrinfo的所有调用重定向到您自己的实现。
示例:
int myaddrinfo(const char* node, const char* service,
const struct addrinfo* hints,
struct addrinfo** res)
{
return EAI_NODATA;
}
int main(int argv, char** argc)
{
spy::initialise(argc, argv);
auto gai_spy = SPY(&getaddrinfo);
auto gai_fake = spy::fake(gai_spy, &myaddrinfo);
...
}
如果需要,您可以调用原始的getaddrinfo:
gai_spy.invoke_real(node, service, hints, res);
您的代码必须使用-fPIC编译为与位置无关的代码才能使其生效。
有关模拟时间(time_t *)的示例中的更多详细信息,请访问: https://github.com/mollismerx/elfspy/wiki/Example-03:-Faking-time