20

到目前为止,我一直在使用一种即兴的单元测试程序——基本上是由批处理文件自动运行的整个单元测试程序负载。尽管其中很多都明确地检查了他们的结果,但更多的作弊 - 他们将结果转储到版本控制的文本文件中。测试结果的任何变化都会被 subversion 标记出来,我可以很容易地识别出变化是什么。许多测试输出点文件或其他形式,使我能够获得输出的可视化表示。

问题是我正在改用cmake。使用 cmake 流程意味着使用源外构建,这意味着将结果转储到共享源/构建文件夹中并与源一起对它们进行版本控制并不真正起作用。

作为替代,我想做是告诉单元测试工具在哪里可以找到预期结果的文件(在源代码树中)并让它进行比较。失败时,它应该提供实际结果和差异列表。

这是可能的,还是我应该采取完全不同的方法?

显然,我可以忽略 ctest 而只是将我一直在做的事情调整到源代码之外的构建。例如,我可以对我的文件夹-where-all-the-builds-live 进行版本控制(当然可以自由使用“ignore”)。那是理智的吗?可能不会,因为每个构建最终都会得到预期结果的单独副本。

此外,收到了有关使用 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);
}

在我需要一个可重用的数据转储函数的地方,它需要一个 type 参数std::ostream&,因此它可以转储到实际结果流。

4

1 回答 1

19

我会使用 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。文件“expected.txt”是源代码树中已知的良好结果。如果存在差异,则会出错,因此您可以看到失败的测试。

这不会打印出差异;CMake 没有隐藏在其中的完整“差异”实现。目前您使用 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。问题是您不应该真正以这种方式更改源代码树。当您第二次运行测试时,它通过了!更好的方法是安装一些视觉差异工具并在您的输出和预期文件上运行它。

于 2010-07-22T12:43:41.050 回答