如何使我的单元测试适应cmake和ctest?

时间:2010-07-22 03:14:09

标签: 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 个答案:

答案 0 :(得分:18)

我使用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。问题是你不应该以这种方式改变源树。当你第二次运行测试时,它会通过!更好的方法是安装一些可视化差异工具并在输出和预期文件上运行它。