Подтвердить что ты не робот

Каков наиболее точный способ тестирования сетевого кода в Linux?

Я хочу покрыть свой код модульными тестами. Это хорошо. Но у меня проблема: у меня есть сетевой код. Код действительно разрешает адреса IPv4 и IPv6 из имен хостов, привязывается к интерфейсам, прослушивает, соединяет и т.д.

Я предполагаю, что существует некоторая среда тестирования C/С++, которая может быть развернута практически на любой рабочей станции или на какой-либо технике программирования, которая позволяет мне:

  • настроить и снести пользовательские сетевые интерфейсы с адресами IPv4 и IPv6.
  • эмулировать различные помехи, такие как потеря пакетов, тайм-аут, сброс соединения и т.д.
  • связывать имена хостов с интерфейсами и разрешать их.

Основная цель - не взаимодействовать и не взаимодействовать с реальными сетевыми интерфейсами на машине.


Что вы можете посоветовать?

4b9b3361

Ответ 1

В системах ELF вы можете использовать elf_hook, чтобы временно заменить реальные версии различных функций своими собственными издеваемыми версиями.

Он позволяет перенаправлять вызовы любой функции из общей библиотеки в вашу произвольную функцию.

  • Создать общую библиотеку, содержащую тестируемый код
  • В тестовой нагрузке динамическая рассылка разделяемой библиотеки (dlopen)
  • Перенаправить символы, которые вы хотите высмеять в своих тестовых функциях (elf_hook)
  • Теперь любые вызовы реальной функции в библиотеке (тестируемый код) будут перенаправлены на вашу издеваемую функцию

Плюс к этому методу заключается в том, что вы по-прежнему можете вызвать исходную функцию, если это необходимо.

  • Если для некоторых тестов вы хотите, чтобы вызов, например getaddrinfo, был успешным, вы можете вызвать версию системы.
  • В других тестах вы можете использовать свою собственную издеваемую версию, например mocked_getaddrinfo, и вернуть ее, что хотите.
  • Вы можете создать столько функций mocked_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;
}

Любой вызов getaddrinfo из тестируемой библиотеки теперь вызовет mocked_getaddrinfo.

Всесторонняя статья автора elf_hook, Энтони Шомихина, здесь.

Ответ 2

Вы можете unit test любой код, если вы готовы потратить время и энергию на это. В основном с модульным тестированием вы заинтересованы в достижении целевого процентного охвата кода и в некоторых областях покрытия MC/DC. В некоторых случаях вам нужно будет написать макет кода (модули, которые экспортируют функции, которые выглядят как API OS API/сокета, вашему тестируемому устройству), которые затем помогут выполнить выполнение через каждый уголок и трещину в "тестируемой единице" (a.c/.cpp), возвращая значения, которые вы указали.

Вам может потребоваться указать различные пути включения тестируемого устройства из остальной части тестового приложения, чтобы избежать конфликтов имен, и вам также может потребоваться использовать макросы предварительного процессора в заголовках тестов, чтобы сделать макет API похожим на реальная сделка с вашим "единицей" и сохранить ее так же, как в производственном коде.

Вы можете протестировать аппаратные драйверы и любой тип низкоуровневого кода.

Например, если ваш код записывает и считывает регистры с отображением памяти, которые, как вы ожидаете, скажут, что логика на основе FPGA изменится, и у вас нет аппаратного обеспечения (или вам сложно создать тестовое условие без фактического перехода на mars), тогда вы можете писать макросы/функции-обертки для чтения и записи в регистры, которые возвратят ваши смешные значения. Раньше использовали CppUTest в прошлом, что было легко освоить. Я предполагаю, что поиск в Google приведет к множеству результатов.