Как адаптировать мои юнит-тесты для cmake и ctest?
До сих пор я использовал импровизированную процедуру модульного тестирования - в основном вся программа модульных тестов запускалась автоматически с помощью пакетного файла. Хотя многие из них явно проверяют свои результаты, гораздо больше читов - они выгружают результаты в текстовые файлы, которые имеют версии. Любое изменение в результатах теста помечается Subversion, и я легко могу определить, что это было за изменение. Многие тесты выводят точечные файлы или какую-либо другую форму, которая позволяет мне получить визуальное представление результатов.
Проблема в том, что я перехожу на использование cmake. Переход к потоку cmake означает использование сборок вне источника, что означает, что удобство выгрузки результатов в общую папку "источник / сборка" и версионирование их вместе с исходным кодом на самом деле не работает.
В качестве замены я хотел бы сообщить инструменту модульного тестирования, где найти файлы ожидаемых результатов (в дереве исходных текстов) и заставить его выполнить сравнение. В случае неудачи он должен предоставить фактические результаты и списки различий.
Возможно ли это, или я должен использовать совершенно другой подход?
Очевидно, я мог игнорировать 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;
}
Типичный юнит-тест теперь выглядит примерно так...
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 для запуска тестов и сравнения результатов. Обычно для программы модульного тестирования вы должны написать add_test(testname testexecutable)
, но вы можете запустить любую команду в качестве теста.
Если вы напишите скрипт "runtest.cmake" и запустите программу модульного тестирования через него, то скрипт runtest.cmake может делать все что угодно - в том числе с использованием 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)
При этом запускается сценарий (cmake -P runtest.cmake) и определяются 2 переменные: TEST_PROG, для которого указан путь к исполняемому файлу теста, и 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
, Файл "Ожидаемый.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 на нем. Проблема в том, что вам не следует менять исходное дерево таким образом. Когда вы запускаете тест во второй раз, он проходит! Лучший способ - установить какой-нибудь инструмент визуального сравнения и запустить его в выходном и ожидаемом файле.