Как адаптировать мои юнит-тесты для cmake и ctest? - PullRequest
20 голосов
/ 22 июля 2010

До сих пор я использовал импровизированную процедуру модульного тестирования - в основном, целую загрузку программ модульного тестирования, автоматически запускаемых пакетным файлом.Хотя многие из них явно проверяют свои результаты, гораздо больше читов - они выгружают результаты в текстовые файлы, которые имеют версии.Любое изменение в результатах теста помечается 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 Ответ

19 голосов
/ 22 июля 2010

Я бы использовал автономный режим сценариев 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 на нем. Проблема в том, что вы не должны менять исходное дерево таким образом. Когда вы запускаете тест во второй раз, он проходит! Лучший способ - установить какой-нибудь инструмент визуального сравнения и запустить его для выходного и ожидаемого файлов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...