Rob*_*nes 30 c++ unit-testing mocking
我有一个类调用getaddrinfo进行DNS查找.在测试期间,我想模拟涉及此系统调用的各种错误情况.模拟这样的系统调用的推荐方法是什么?我正在使用Boost.Test进行单元测试.
Kal*_*son 27
在这种情况下,您不需要模拟getaddrinfo,而是需要在不依赖其功能的情况下进行测试.帕特里克和诺亚都有优点,但你至少有两个选择:
由于您已经在类中拥有了对象,因此可以子类化以进行测试.例如,假设以下是您的实际类:
class DnsClass {
int lookup(...);
};
int DnsClass::lookup(...) {
return getaddrinfo(...);
}
Run Code Online (Sandbox Code Playgroud)
然后,为了测试,你会像这样子类:
class FailingDnsClass {
int lookup(...) { return 42; }
};
Run Code Online (Sandbox Code Playgroud)
您现在可以使用FailingDnsClass子类生成错误,但仍会在发生错误情况时验证一切正常.在这种情况下,依赖注入通常是您的朋友.
注意:这与Patrick的答案非常相似,但如果您尚未设置依赖注入,则不会(希望)涉及更改生产代码.
在C++中,您还有链接时间接缝,Michael Feathers在有效使用旧版代码中描述了这些接缝.
基本思想是利用链接器和构建系统.编译单元测试时,您自己的版本中的链接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;
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
然后进行测试:
$ g++ test.cpp lib.cpp -o test
$ ./test
RV:42
Run Code Online (Sandbox Code Playgroud)
Pat*_*ick 13
查找"依赖注入"的模式.
依赖注入的工作原理如下:代码不是直接在代码中调用getaddrinfo,而是使用具有虚拟方法"getaddrinfo"的接口.
在实际代码中,调用者传递接口的实现,该接口将接口的虚拟方法"getaddrinfo"映射到real :: getaddrinfo函数.
在单元测试中,调用者传递一个可以模拟失败,测试错误条件的实现,......简短:模拟你想要模拟的任何东西.
编辑:阅读Michael Feathers的"有效使用遗留代码"以获取更多提示.
def*_*ode 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;
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
3.使用相同的名称定义您自己的中间函数.然后,如果您愿意,您仍然可以拨打原件.使用某些宏来帮助重复更容易.如果你想模拟可变参数函数(等)printf,你还必须使用gnu扩展open.
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;
Run Code Online (Sandbox Code Playgroud)