到目前为止,我已经使用了一个临时的单元测试程序 - 基本上是由批处理文件自动运行的全部单元测试程序。尽管其中很多都明确地检查了他们的结果,但还有更多的作弊行为 - 他们将结果转储到版本控制的文本文件中。测试结果中的任何更改都会被颠覆标记,我可以轻松识别更改是什么。许多测试输出点文件或其他某种形式,使我能够获得输出的可视化表示。
问题是我要改用 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&
类型的参数,因此它可以转储到实际结果流。
最佳答案
我会使用 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 中没有隐藏完整的“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。问题是您不应该真正以这种方式更改源代码树。当您第二次运行测试时,它就通过了!更好的方法是安装一些视觉差异工具并在输出和预期文件上运行它。
关于cmake - 如何使我的单元测试适应 cmake 和 ctest?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3305545/