关于模拟系统调用的建议

Rob*_*nes 30 c++ unit-testing mocking

我有一个类调用getaddrinfo进行DNS查找.在测试期间,我想模拟涉及此系统调用的各种错误情况.模拟这样的系统调用的推荐方法是什么?我正在使用Boost.Test进行单元测试.

Kal*_*son 27

在这种情况下,您不需要模拟getaddrinfo,而是需要在不依赖其功能的情况下进行测试.帕特里克和诺亚都有优点,但你至少有两个选择:

选项1:要测试的子类

由于您已经在类中拥有了对象,因此可以子类化以进行测试.例如,假设以下是您的实际类:

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的答案非常相似,但如果您尚未设置依赖注入,则不会(希望)涉及更改生产代码.

选项2:使用链接接缝

在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)

  • 我使用选项2来测试生产代码.只要您不需要在单元测试中调用真实版本,它就可以工作. (6认同)

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)