C++ 在Linux上测试网络代码最准确的方法是什么?

C++ 在Linux上测试网络代码最准确的方法是什么?,c++,c,linux,unit-testing,network-programming,C++,C,Linux,Unit Testing,Network Programming,我想用单元测试来覆盖我的代码。这是件好事。但我有一个问题-我有一个网络代码。代码从主机名解析IPv4和IPv6地址、绑定到接口、侦听、连接等 我假设存在一些可以部署在几乎任何工作站上的C/C++测试框架,或者一些编程技术允许我: 设置和拆除具有IPv4和IPv6地址的自定义网络接口 模拟不同的干扰行为,如丢失数据包、超时、连接断开等 将主机名绑定到接口并解析它们 主要目标不是与机器上的真实网络接口进行交互或弄乱 你有什么建议?如果你愿意花时间和精力去做,你可以对任何代码进行单元测试。基本上

我想用单元测试来覆盖我的代码。这是件好事。但我有一个问题-我有一个网络代码。代码从主机名解析IPv4和IPv6地址、绑定到接口、侦听、连接等

我假设存在一些可以部署在几乎任何工作站上的C/C++测试框架,或者一些编程技术允许我:

  • 设置和拆除具有IPv4和IPv6地址的自定义网络接口
  • 模拟不同的干扰行为,如丢失数据包、超时、连接断开等
  • 将主机名绑定到接口并解析它们
主要目标不是与机器上的真实网络接口进行交互或弄乱



你有什么建议?

如果你愿意花时间和精力去做,你可以对任何代码进行单元测试。基本上,通过单元测试,您对实现目标的代码覆盖率和某些行业的MC/DC覆盖率感兴趣。在某些情况下,您需要编写模拟代码(将类似于OS API/socket API的函数导出到您的被测单元的模块),然后通过返回您告诉它的值,帮助驱动执行通过“被测单元”(a.c/.cpp文件)中的每个角落

您可能需要为测试中的单元指定不同于测试应用程序其余部分的包含路径,以避免名称冲突,并且您可能还必须在测试头中使用预处理器宏,以使模拟API在您的“单元”中看起来像是真实的,并使其与生产代码中的相同

您可以测试硬件驱动程序和任何类型的低级代码


例如,如果您的代码正在写入和读取内存映射寄存器,您希望这些寄存器(基于FPGA的逻辑)发生变化,而您没有硬件(或者您很难在不实际前往火星的情况下生成测试条件),然后,您可以编写宏/包装器函数,用于读取和写入将返回模拟值的寄存器。在过去,我们使用了CppUTest,它很容易学习。我想谷歌搜索会找到很多结果

在ELF系统上,您可以使用自己的模拟版本临时替换各种功能的真实版本

它允许您将对共享库中任何函数的调用重定向到您自己的任意函数

  • 创建包含测试代码的共享库
  • 在测试中,动态加载共享库(
    dlopen
  • 将要模拟的符号重定向到测试函数(
    elf\u hook
  • 现在,对库中真实函数(测试中的代码)的任何调用都将重定向到模拟函数
此方法的一个优点是,您仍然可以在需要时调用原始函数

  • 如果对于某些测试,您希望调用,例如
    getaddrinfo
    ,则可以调用系统版本
  • 在其他测试中,您可以使用自己的模拟版本,例如
    mocked\u getaddrinfo
    ,并让它返回您想要的任何内容
  • 您可以创建任意多个
    mock_getaddrinfo
    函数来测试多个场景
elf_hook具有以下签名:

void* elf_hook(char const* library_filename, 
               void const* library_address, 
               char const* function_name, 
               void const* substitution_address);
您可以这样使用它:

#include <dlfcn.h>
#include "elf_hook.h"

void do_stuff(); // from the library under test (do_stuff calls getaddrinfo)

// our mocked function which will alter the behaviour inside do_stuff()
int mocked_getaddrinfo(const char* node, 
                       const char* service,
                       const struct addrinfo* hints,
                       struct addrinfo** res)
{
    // return a broken value to test a getaddrinfo failure
    return 42;
}

// another version which actually calls the real function
int real_getaddrinfo(const char* node, 
                     const char* service,
                     const struct addrinfo* hints,
                     struct addrinfo** res)
{
    // the real getaddrinfo is available to us here, we only replace it in the shared lib
    return getaddrinfo(node, service, hints, res);
}

int main()
{
    const char* lib_path = "path/to/library/under/test.so";

    // load the library under test
    void* lib_handle = dlopen(lib_path, RTLD_LAZY);

    // test 1: getraddrinfo is broken
    //--------------------------------
    // replace getaddrinfo with our 'mocked_getaddrinfo' version
    elf_hook(lib_path, LIBRARY_ADDRESS_BY_HANDLE(lib_handle), 
             "getaddrinfo", mocked_getaddrinfo);

    // call a function in the library under test where getaddrinfo fails
    do_stuff();

    // test 2: getraddrinfo is the system version
    //--------------------------------
    // replace getaddrinfo with our 'real_getaddrinfo' version 
    elf_hook(lib_path, LIBRARY_ADDRESS_BY_HANDLE(lib_handle), 
             "getaddrinfo", real_getaddrinfo);

    // call the same function in the library, now getaddrinfo works
    do_stuff();

    dlclose(lib_handle);
    return 0;
}
#包括
#包括“elf_hook.h”
void do_stuff();//来自测试库(do_stuff调用getaddrinfo)
//我们的模拟函数将改变do_stuff()内部的行为
int mock_getaddrinfo(常量字符*节点,
常量字符*服务,
常量结构addrinfo*提示,
结构addrinfo**res)
{
//返回一个断开的值以测试getaddrinfo失败
返回42;
}
//另一个版本实际上调用了实函数
int real_getaddrinfo(常量字符*节点,
常量字符*服务,
常量结构addrinfo*提示,
结构addrinfo**res)
{
//真正的getaddrinfo在这里是可用的,我们只在共享库中替换它
返回getaddrinfo(节点、服务、提示、res);
}
int main()
{
const char*lib_path=“path/to/library/under/test.so”;
//加载正在测试的库
void*lib\u handle=dlopen(lib\u路径,RTLD\u惰性);
//测试1:getraddrinfo已损坏
//--------------------------------
//将getaddrinfo替换为我们的“mocked_getaddrinfo”版本
elf_钩子(lib_路径、lib_地址、lib_句柄),
“getaddrinfo”,mock_getaddrinfo);
//在getaddrinfo失败的测试库中调用函数
做某事;
//测试2:getraddrinfo是系统版本
//--------------------------------
//将getaddrinfo替换为我们的“real_getaddrinfo”版本
elf_钩子(lib_路径、lib_地址、lib_句柄),
“getaddrinfo”,real_getaddrinfo);
//在库中调用相同的函数,现在getaddrinfo可以工作了
做某事;
dlclose(lib_手柄);
返回0;
}
在测试库中对
getaddrinfo
的任何调用现在都将调用
mocked\u getaddrinfo


elf_hook作者Anthony Shoumikhin的一篇综合文章是。

(不是重复的,但这可能会有帮助。)是我,还是您试图测试的行为超出了单元测试的范围,进入了集成测试?我想这是一个迂腐的观点,但可能会改变答案的范围。对于大多数开发人员来说,这是两个截然不同的主题。@ChrisCM在我看来,这不是一个集成测试,因为我不想进行真正的部署,也不想使用多台机器。另外,我不想测试整个产品,只想测试一些实现部分,如事件循环、网络服务、流媒体。它应该是一个二进制,完成所有的工作。在你看来,它看起来像是集成测试吗?我是