Как адаптировать мои модульные тесты к cmake и ctest?
До сих пор я использовал импровизированную модульную процедуру тестирования - в основном, весь пакет программ unit test запускался автоматически пакетным файлом. Хотя многие из них явно проверяют свои результаты, намного больше обманывают - они выгружают результаты в текстовые файлы, которые версируются. Любое изменение результатов теста помечено подрывной деятельностью, и я могу легко определить, что такое изменение. Многие из тестов выводят файлы точек или другую форму, которая позволяет мне получить визуальное представление вывода.
Проблема в том, что я переключаюсь на использование cmake. Переход с потоком cmake означает использование сборок вне источника, что означает, что удобство распаковки получается в общей папке источника/сборки, а их версия вместе с источником не работает.
В качестве замены, я бы хотел сказать, что инструмент unit test найдет файлы ожидаемых результатов (в исходном дереве) и попросит его выполнить сравнение. При сбое он должен предоставлять фактические результаты и списки различий.
Возможно ли это, или я должен использовать совершенно другой подход?
Очевидно, я мог бы игнорировать ctest и просто приспосабливать то, что я всегда делал, к сборке вне источника. Я мог бы, например, реализовать версию моей папки-где-все-строить-жить (с либеральным использованием "игнорировать", конечно). Это здорово? Вероятно, нет, так как каждая сборка будет иметь отдельную копию ожидаемых результатов.
Кроме того, любые советы по рекомендуемому способу проведения модульного тестирования с помощью cmake/ctest получаются графоподобно. Я потратил немного времени на cmake, но не потому, что это плохо, а потому, что я не понимал, как лучше работать с ним.
ИЗМЕНИТЬ
В конце концов, я решил как можно проще сохранить сторону cmake/ctest модуля. Чтобы проверить фактические данные по ожидаемым результатам, в моей библиотеке я нашел дом для следующей функции...
bool Check_Results (std::ostream &p_Stream ,
const char *p_Title ,
const char **p_Expected,
const std::ostringstream &p_Actual )
{
std::ostringstream l_Expected_Stream;
while (*p_Expected != 0)
{
l_Expected_Stream << (*p_Expected) << std::endl;
p_Expected++;
}
std::string l_Expected (l_Expected_Stream.str ());
std::string l_Actual (p_Actual.str ());
bool l_Pass = (l_Actual == l_Expected);
p_Stream << "Test: " << p_Title << " : ";
if (l_Pass)
{
p_Stream << "Pass" << std::endl;
}
else
{
p_Stream << "*** FAIL ***" << std::endl;
p_Stream << "===============================================================================" << std::endl;
p_Stream << "Expected Results For: " << p_Title << std::endl;
p_Stream << "-------------------------------------------------------------------------------" << std::endl;
p_Stream << l_Expected;
p_Stream << "===============================================================================" << std::endl;
p_Stream << "Actual Results For: " << p_Title << std::endl;
p_Stream << "-------------------------------------------------------------------------------" << std::endl;
p_Stream << l_Actual;
p_Stream << "===============================================================================" << std::endl;
}
return l_Pass;
}
Типичный unit test теперь выглядит примерно как...
bool Test0001 ()
{
std::ostringstream l_Actual;
const char* l_Expected [] =
{
"Some",
"Expected",
"Results",
0
};
l_Actual << "Some" << std::endl
<< "Actual" << std::endl
<< "Results" << std::endl;
return Check_Results (std::cout, "0001 - not a sane test", l_Expected, l_Actual);
}
В тех случаях, когда мне нужна повторно используемая функция демпинга данных, она принимает параметр типа std::ostream&
, поэтому он может сбрасывать поток фактических результатов.
Ответы
Ответ 1
Я бы использовал автономный сценарий CMake для запуска тестов и сравнения выходов. Обычно для программы unit test вы должны писать add_test(testname testexecutable)
, но вы можете запускать любую команду в качестве теста.
Если вы пишете script "runtest.cmake" и выполните с помощью этой программы unit test, то runtest.cmake script может делать все, что угодно, в том числе с помощью утилиты cmake -E compare_files
. В файле CMakeLists.txt вы хотите получить следующее:
enable_testing()
add_executable(testprog main.c)
add_test(NAME runtestprog
COMMAND ${CMAKE_COMMAND}
-DTEST_PROG=$<TARGET_FILE:testprog>
-DSOURCEDIR=${CMAKE_CURRENT_SOURCE_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/runtest.cmake)
Выполняется script (cmake -P runtest.cmake) и определяет 2 переменные: TEST_PROG, устанавливается путь к исполняемому файлу test и SOURCEDIR, устанавливается в текущий каталог источника. Сначала вам нужно знать, какая программа запускаться, вторая - знать, где найти ожидаемые файлы результатов теста. Содержимое runtest.cmake
будет:
execute_process(COMMAND ${TEST_PROG}
RESULT_VARIABLE HAD_ERROR)
if(HAD_ERROR)
message(FATAL_ERROR "Test failed")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} -E compare_files
output.txt ${SOURCEDIR}/expected.txt
RESULT_VARIABLE DIFFERENT)
if(DIFFERENT)
message(FATAL_ERROR "Test failed - files differ")
endif()
Первая execute_process
запускает тестовую программу, которая выведет "output.txt". Если это работает, то следующий execute_process
эффективно работает cmake -E compare_files output.txt expected.txt
. Файл "expected.txt" - это хороший результат в исходном дереве. Если есть различия, это приводит к ошибкам, поэтому вы можете увидеть неудавшийся тест.
Что это не делает, это распечатать различия; У CMake нет полной реализации "diff", скрытой внутри него. В настоящий момент вы используете Subversion, чтобы увидеть, какие строки изменились, поэтому очевидным решением является изменение последней части на:
if(DIFFERENT)
configure_file(output.txt ${SOURCEDIR}/expected.txt COPYONLY)
execute_process(COMMAND svn diff ${SOURCEDIR}/expected.txt)
message(FATAL_ERROR "Test failed - files differ")
endif()
Это перезаписывает исходное дерево с результатом сборки при сбое, затем запускает svn diff. Проблема в том, что вы не должны действительно менять дерево источников таким образом. Когда вы запускаете тест во второй раз, он проходит! Лучше всего установить некоторый инструмент визуального разграничения и запустить его на вашем выходе и ожидаемом файле.