CMake和CTest:make test不能构建测试

110
我正在尝试在 CMake 中使用 CTest,以便使用 make test 目标自动运行我的一些测试。问题是 CMake 不“理解”我想运行的测试必须被构建,因为它是项目的一部分。因此,我正在寻找一种明确指定这种依赖关系的方法。
11个回答

97

可以说这是一个CMake的bug(此前在这里有记录),导致它不能直接使用。解决方法如下:

add_test(TestName ExeName)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}
                  DEPENDS ExeName)

然后您可以运行make check,它将编译并运行测试。如果您有多个测试,则必须在上述行中使用DEPENDS exe1 exe2 exe3 ...


2
所以我猜“make test”目标将保持未使用状态,因为似乎您必须在add_custom_target命令中选择不同的目标名称? - claf
2
@rq - 但是如果我有多个项目(其中一个CMakeLists.txt是另一个子项目),我该怎么办,以便每个项目都定义“check”目标并且它们可能会发生冲突? - Artyom
4
在这种情况下,你最好使用相应的短语 "make all test"。实际上,这也是我做的事情。 - richq
4
实际上,一些人认为这是 cmake 的一个特性(而不是错误),你可以运行 "make test" 并直接运行测试,而无需首先进行任何重新构建... - DLRdave
1
这在IDE构建中不起作用,因为需要将“-C Debug”或类似的参数传递给ctest。 CMAKE_CFG_INTDIR无法工作,因为测试命令在配置时未知。 - Brent
显示剩余2条评论

60

其实有一种方法可以使用make test。你需要将测试可执行文件的构建定义为其中一个测试,然后在测试之间添加依赖关系。具体是这样的:

ADD_TEST(ctest_build_test_code
         "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} --target test_code)
ADD_TEST(ctest_run_test_code test_code)
SET_TESTS_PROPERTIES(ctest_run_test_code
                     PROPERTIES DEPENDS ctest_build_test_code)

12
这是唯一一个可以扩展而且不强制你构建“make all”目标才能运行测试的工具。一个可能的缺点是,二进制文件构建错误的详细信息只会显示在生成的LastTest.log 文件中,而不会显示在stdout/stderr上。 - Dave Abrahams
2
好的回答!不过你应该将配置添加到构建目标中。否则无法在所有配置中运行测试。add_test( NAME "${ARGV0}_BUILD" COMMAND "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} --target ${target} "--config" "$<CONFIG>" ) - Daniel
2
这会在测试报告中填充一堆虚假的测试,影响其可读性。 - user246672
如果您使用的是CMake >= 3.7,推荐的方法是使用fixtures。请参见下面的我的答案 - John Freeman

18

如果你正在使用 CMake >= 3.7,推荐的方法是使用 fixtures


如果您使用 CMake 版本大于等于3.7,则建议使用“fixtures”进行测试:
add_executable(test test.cpp)
add_test(test_build
  "${CMAKE_COMMAND}"
  --build "${CMAKE_BINARY_DIR}"
  --config "$<CONFIG>"
  --target test
)
set_tests_properties(test_build PROPERTIES FIXTURES_SETUP    test_fixture)
add_test(test test)
set_tests_properties(test       PROPERTIES FIXTURES_REQUIRED test_fixture)

以下是操作步骤:

  • 添加一个可执行目标test,其源文件为test.cpp
  • 添加一个名为test_build的“测试”,运行Cmake来构建test目标。
  • test_build测试标记为test_fixture夹具的设置任务。
  • 添加一个名为test的测试,它只运行test可执行文件。
  • test测试标记为需要test_fixture夹具。

因此,每次要运行test测试时,它首先运行测试test_build,该测试会构建必要的可执行文件。


如果 $<CONFIG> 没有设置,--target 将成为 --config 的参数。 - loshad vtapkah
我相信 $<CONFIG> 总是非空的。它是一个配置名称的生成器表达式:https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#id2 无论如何,我会编辑答案将其用引号括起来,因为这没有任何区别。 - John Freeman
你如何运行 cmake?我是这样做的:mkdir build; cd build; cmake ..; make。看起来没有任何默认值,所有相关变量都为空,直到手动设置 CMAKE_BUILD_TYPE 为止。(目前在 Debian 10 上,未检查其他平台) - loshad vtapkah
1
我不是完全确定:您能同时在同一构建目录上运行多个“make”进程吗?这种情况可能会发生,因为测试可以由“ctest”并发执行。 - Nikita Petrenko

16
我使用richq的一个变体。在顶层的CMakeLists.txt中,我添加了一个名为build_and_test的自定义目标,用于构建和运行所有测试:
find_package(GTest)
if (GTEST_FOUND)
    enable_testing()
    add_custom_target(build_and_test ${CMAKE_CTEST_COMMAND} -V)
    add_subdirectory(test)
endif()

test/目录下的各个子项目的CMakeLists.txt文件中,我将每个测试可执行文件添加为build_and_test的依赖项:
include_directories(${CMAKE_SOURCE_DIR}/src/proj1)
include_directories(${GTEST_INCLUDE_DIRS})
add_executable(proj1_test proj1_test.cpp)
target_link_libraries(proj1_test ${GTEST_BOTH_LIBRARIES} pthread)
add_test(proj1_test proj1_test)
add_dependencies(build_and_test proj1_test)

使用这种方法,我只需要运行make build_and_test而不是make test(或者make all test),它的好处是只编译测试代码及其依赖项。很遗憾我不能使用目标名称test。在我的情况下,这并不太糟糕,因为我有一个顶层脚本,通过调用cmakemake进行带外调试和发布(以及交叉编译)构建,并将test转换为build_and_test
显然,GTest并不是必需的,我只是碰巧使用/喜欢Google Test,并想分享一个完整的使用CMake/CTest的示例。在我看来,这种方法还有一个好处,就是允许我使用ctest -V,它会在测试运行时显示Google Test输出:
1: Running main() from gtest_main.cc
1: [==========] Running 1 test from 1 test case.
1: [----------] Global test environment set-up.
1: [----------] 1 test from proj1
1: [ RUN      ] proj1.dummy
1: [       OK ] proj1.dummy (0 ms)
1: [----------] 1 test from proj1 (1 ms total)
1:
1: [----------] Global test environment tear-down
1: [==========] 1 test from 1 test case ran. (1 ms total)
1: [  PASSED  ] 1 test.
1/2 Test #1: proj1_test .......................   Passed    0.03 sec

在这个例子中,有没有办法让make test执行ctest -V的操作而不是执行ctest? ctest输出看起来非常不完整,只显示有一个测试。 - Rajiv

7

1
运行 make check 时,这将构建所有可执行文件。对于编译时间占主导地位的测试,这使得 ctest -R 无用。 - usr1234567

7

节省你的头痛:

make all test

这对我来说很容易使用,会在运行测试前构建依赖项。鉴于它的简单性,这几乎使原生的make test功能变得更加方便,因为它让您有选择地运行最后一次编译测试,即使您的代码出现了错误。


1
无法与CDash一起使用。您必须调用make all && ctest,然后构建不是上传测试的一部分。因此,构建警告或错误不可见。 - usr1234567
3
如果你想要并行构建,这个方法也不是很有效,因为这两个构建步骤会同时运行:你需要使用命令make -j4 all && make test。而且如果使用非 Make 构建工具,这种方法也会不太稳定。 - poolie

3
对于CMake 3.10或更高版本,另一种选项是使用 TEST_INCLUDE_FILES 目录属性来设置在运行测试之前触发构建的脚本。在你的最外层的 CMakeLists.txt 中加入以下代码:
set_property(DIRECTORY APPEND
    PROPERTY TEST_INCLUDE_FILES "${CMAKE_CURRENT_BINARY_DIR}/BuildTestTarget.cmake")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/BuildTestTarget.cmake"
   "execute_process(COMMAND \"${CMAKE_COMMAND}\""
   " --build \"${CMAKE_BINARY_DIR}\""
   " --config \"\$ENV{CMAKE_CONFIG_TYPE}\")")

实际测试配置通过环境变量 CMAKE_CONFIG_TYPE 传递到构建中。可选择添加 --target 选项来仅构建测试所需的目标。


2

这是我制定并一直在使用的内容:

set(${PROJECT_NAME}_TESTS a b c)

enable_testing()
add_custom_target(all_tests)
foreach(test ${${PROJECT_NAME}_TESTS})
        add_executable(${test} EXCLUDE_FROM_ALL ${test}.cc)
        add_test(NAME ${test} COMMAND $<TARGET_FILE:${test}>)
        add_dependencies(all_tests ${test})
endforeach(test)

build_command(CTEST_CUSTOM_PRE_TEST TARGET all_tests)
string(CONFIGURE \"@CTEST_CUSTOM_PRE_TEST@\" CTEST_CUSTOM_PRE_TEST_QUOTED ESCAPE_QUOTES)
file(WRITE "${CMAKE_BINARY_DIR}/CTestCustom.cmake" "set(CTEST_CUSTOM_PRE_TEST ${CTEST_CUSTOM_PRE_TEST_QUOTED})" "\n")

YMMV


1

Derrick的答案,简化并加注释:

# It is impossible to make target "test" depend on "all":
# https://gitlab.kitware.com/cmake/cmake/-/issues/8774
# Set a magic variable in a magic file that tells ctest
# to invoke the generator once before running the tests:
file(WRITE "${CMAKE_BINARY_DIR}/CTestCustom.cmake"
    "set(CTEST_CUSTOM_PRE_TEST ${CMAKE_MAKE_PROGRAM})\n"
)

这并不是完全正确的解决方案,因为它无法解决运行ninja all test时的并发问题,如果有人这样做的话。相反,现在你有两个ninja进程。

(顺便说一句,我也在这里分享了这个解决方案here。)


-4

所有上面的答案都是完美的。但实际上,CMake使用CTest作为其测试工具,因此完成任务的标准方法(我认为)是:

enable_testing ()
add_test (TestName TestCommand)
add_test (TestName2 AnotherTestCommand)

然后运行cmakemake来构建目标。之后,您可以运行make test,或者只需运行

ctest

你将会得到结果。这已经在 CMake 2.8 下测试过。

详细信息请查看:http://cmake.org/Wiki/CMake/Testing_With_CTest#Simple_Testing


5
因为有时候你只想构建实际运行的测试所需的目标,所以被点踩了。 - Dave Abrahams
12
这个回答似乎误解了问题:提问者已经按照这个回答的建议在使用CTest、enable_testing()、add_test()等。问题是他必须在运行测试之前手动发出构建命令。他希望"make test"目标可以根据需要自动构建测试可执行文件。 - bames53

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接