Консультации по системным вызовам - PullRequest
30 голосов
/ 27 мая 2010

У меня есть класс, который вызывает getaddrinfo для поиска DNS. Во время тестирования я хочу смоделировать различные состояния ошибки, связанные с этим системным вызовом. Каков рекомендуемый метод для поддельных системных вызовов? Я использую Boost.Test для моего юнит-тестирования.

Ответы [ 6 ]

27 голосов
/ 28 мая 2010

В этом случае вам не нужно издеваться над getaddrinfo, скорее, вам нужно тестировать, не полагаясь на его функциональность. У Патрика и Ноа есть хорошие очки, но у вас есть как минимум два других варианта:

Вариант 1: Подкласс для тестирования

Поскольку у вас уже есть свой объект в классе, вы можете тестировать подкласс. Например, предположим, что фактический класс является следующим:

class DnsClass {
    int lookup(...);
};

int DnsClass::lookup(...) {
    return getaddrinfo(...);
}

Затем, для тестирования, вы бы подклассы вроде этого:

class FailingDnsClass {
    int lookup(...) { return 42; }
};

Теперь вы можете использовать подкласс FailingDnsClass для генерации ошибок, но при этом проверить, что все работает правильно, когда возникает ошибка. В этом случае инъекция зависимости часто является вашим другом.

ПРИМЕЧАНИЕ. Это очень похоже на ответ Патрика, но не предполагает (надеюсь) изменения производственного кода, если вы еще не настроены на внедрение зависимостей.

Вариант 2: использовать шов ссылки

В C ++ у вас также есть швы во время компоновки, которые Майкл Фезерс описывает в Эффективная работа с устаревшим кодом .

Основная идея - использовать компоновщик и вашу систему сборки. При составлении модульных тестов, укажите в вашей собственной версии 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
13 голосов
/ 28 мая 2010

Поиск шаблонов для «Внедрения зависимостей».

Внедрение зависимостей работает следующим образом: вместо вызова getaddrinfo непосредственно в вашем коде код использует интерфейс с виртуальным методом "getaddrinfo".

В реальном коде вызывающая сторона передает реализацию интерфейса, который отображает виртуальный метод "getaddrinfo" интерфейса на функцию real :: getaddrinfo.

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

РЕДАКТИРОВАТЬ: Прочитайте «Эффективная работа с унаследованным кодом» от Michael Feathers для получения дополнительных советов.

10 голосов
/ 28 мая 2010

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 . Определите свою собственную посредническую функцию с тем же именем. Тогда вы все равно можете назвать оригинал, если хотите. Это намного проще с некоторыми макросами, чтобы помочь с повторением. Также вам придется использовать расширения GNU, если вы хотите высмеивать различные функции (printf, 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;
2 голосов
/ 04 декабря 2014

В системах 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 Энтони Шоухихина: здесь .

1 голос
/ 28 мая 2010

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

Что вам следует сделать, это позвонить посреднику. Затем вы можете издеваться над посредником во время тестирования и просто перейти к фактической функции в производстве. Вы можете даже подумать о создании класса, который взаимодействует с этой функцией и другими подобными ей и предоставляет более общий интерфейс для вашей программы. Этот класс на самом деле ничего не делает, кроме переадресации вызовов, большую часть времени, но во время теста он может быть эффективно смоделирован, и вы можете протестировать все, что его использует.

Суть в том, чтобы сохранить такие вещи, вещи, которые невозможно протестировать, обернуть во что-то настолько тривиальное, что на самом деле не нужно тестировать, а затем смоделировать эту оболочку для тестирования более сложных взаимодействий. KISS особенно важен здесь.

0 голосов
/ 18 апреля 2018

Отказ от ответственности: я написал 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

...