Написание C++ модульных тестов вблизи объявлений функций/классов

Когда дело доходит до написания модульных тестов для функций или классов в C++, кажется популярным писать их в отдельных исходных файлах из исходных деклараций и определений (например, math_test.cpp для math.hpp/math.cpp). Интересно, может ли их написать около деклараций, может быть лучше по следующим причинам:

  • пользователи могут увидеть, как использовать функцию или класс из своего модульного теста
  • разработчики могут четко определить, имеет ли функция или класс единичный тест или нет

Например, я задаюсь вопросом, могу ли я поместить единичный тест в объявление функции в заголовочном файле следующим образом:

// plus.hpp
int plus(int a, int b);
UNITTEST{ assert(plus(1, 2) == 3); }

в то время как исходный файл выглядит как обычно:

// plus.cpp 
int plus(int a, int b) { return a + b; }

и только когда я определяю конкретный макрос (например, ENABLE_UNITTEST), я ожидаю, что код UNITTEST будет оцениваться компилятором и исполнен исполняемым исполнятелем тестового бегуна.

Мои вопросы:

  1. Является ли хорошей практикой писать модульные тесты рядом с объявлениями вроде этого?
  2. Существует ли существующая C++ среда тестирования, которая поддерживает такие тесты? (Я думаю, что это приведет к ошибке переопределения, когда plus.hpp будет включен в несколько исходных файлов, если структура не реализована должным образом)
  3. Если ответ на 2. НЕТ, возможно ли реализовать такую структуру тестирования?
3 ответа

Является ли хорошей практикой писать модульные тесты рядом с объявлениями вроде этого?

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

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

Кроме того, некоторые модульные тесты должны поддерживать состояние во время прогона (с этим связаны функции настройки и размыкания общей тестовой среды). Смешивание заполнителя для состояния с кодом полезной нагрузки еще больше осложнит ситуацию.

Существует ли существующая C++ среда тестирования, которая поддерживает такие тесты?

Не то, что я знаю из.

Если ответ на 2. НЕТ, возможно ли реализовать такую структуру тестирования?

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


Существует ли существующая C++ среда тестирования, которая поддерживает такие тесты? (Я думаю, что это приведет к ошибке переопределения, когда plus.hpp будет включен в несколько исходных файлов, если структура не реализована должным образом)

Вы можете реализовать такой путь со многими структурами exitsting (если не все), например, google test:

int plus(int a, int b);

#ifdef UNITTEST_ENABLED

TEST(Functions,plus)
{
 EXPECT_EQ( 4, plus( 2, 2 ) );
}
#endif // UNITTEST_ENABLED

вам просто нужно правильно настроить свою систему сборки для компиляции источников для тестов с помощью макроса UNITTEST_ENABLED

Хотя я согласен с ответом @dasblinkenlight и считаю, что это не очень хорошая практика, чтобы смешивать тестовый код с обычными источниками. Например, если вы правильно покроете свою функцию plus() в своем коде, вы должны проверить положительный результат с положительным, положительным отрицательным, отрицательным с отрицательным и угловым случаем (max int + 0) и т.д. После того, как вы это сделаете, ваш фактический код функции будет быть хорошо скрытым внутри тестов, чтобы ваш источник стал нечитаемым.

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


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

Ваш способ делать что-то очень запутанно, если ваши тестовые примеры становятся длиннее строки или двух (или имеют несколько тестов). Не говоря уже о попытке найти код, и общая читаемость будет значительно уменьшена.

licensed under cc by-sa 3.0 with attribution.