第24章:测试

构建项目的后续操作是测试创建的工程。CMake软件套件包括CTest工具,可以用于自动化测试,甚至可以完成配置、构建、测试,甚至提交结果到仪表盘的整个过程。本章首先介绍了如何使用CMake来定义测试,并使用ctest命令行工具来执行测试。自动化整个配置-构建-测试过程中有技术,本章后面将讨论这些技术。

24.1. 定义和执行一个简单的测试

在CMake项目中定义测试的第一步是在顶级CMakeLists.txt文件的某个地方调用enable_testing()。这个函数的作用是让CMake在CMAKE_CURRENT_BINARY_DIR中生成一个CTest输入文件,其中包含在项目中所有测试的详细信息(更准确地说,是在当前目录范围内定义的那些测试)。可以在子目录中调用enable_testing(),但是如果不在顶层调用enable_testing(), CTest输入文件将不会在构建树的顶层创建。

使用add_test()命令定义测试:

add_test(NAME testName
 COMMAND command [arg...]
 [CONFIGURATIONS config1 [config2...]]
 [WORKING_DIRECTORY dir]
)

这个命令添加了一个名为testName的新测试,使用给定的参数运行指定的命令。默认情况下,如果命令返回的退出码为0,测试则认为通过。更灵活的通过/失败支持,将在下一节中讨论。

该命令可以是可执行文件的完整路径,也可以是项目中定义的可执行目标的名称。使用目标名称时,CMake将自动替换可执行文件的实际路径。这在使用多配置生成器(如Xcode或Visual Studio)时特别有用,这些生成器中,可执行文件的位置将特定于配置。下面展示了一个项目的示例:

cmake_minimum_required(VERSION 3.0)
project(CTestExample)
enable_testing()

add_executable(testapp testapp.cpp)
add_test(NAME noArgs COMMAND testapp)

使用目标的实际位置自动替换,并不会扩展到命令参数,只有命令本身支持这种替换。如果需要将目标的位置作为命令行参数提供,则可以使用生成器表达式。例如:

add_executable(app1 ...)
add_executable(app2 ...)

add_test(NAME withArgs COMMAND app1 $<TARGET_FILE:app2>)

运行测试时,用户可以指定测试哪个配置。当项目使用单个配置生成器时,配置不必匹配生成类型。特别是,如果没有提供配置,则假设配置为空。如果没有可选配置关键字CONFIGURATIONS,不管构建类型或用户请求的配置是什么,程序将测试所有配置。如果给定了CONFIGURATIONS关键字,则仅对列出相应配置的运行测试。请注意,空配置仍然认为有效,因此要在该场景中运行测试,空字符串是CONFIGURATIONS的内容之一。

例如,要添加一个只对具有调试信息配置执行的测试,可以列出Debug和RelWithDebInfo配置。添加空字符串也会在没有指定配置时运行:

add_test(NAME debugOnly
 COMMAND testapp
 CONFIGURATIONS Debug RelWithDebInfo ""
)

大多数情况下,配置关键字CONFIGURATIONS是不需要的,测试将对所有配置执行,包括空配置。

默认情况下,测试将在CMAKE_CURRENT_BINARY_DIR目录中运行,可以用WORKING_DIRECTORY选项指定测试运行的位置。在不同的目录中运行相同的可执行文件,以获得不同的输入文件集,从而不必指定相应的命令行参数。

add_test(NAME foo
 COMMAND testapp
 WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/foo
)
add_test(NAME bar
 COMMAND testapp
 WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/bar
)

如果指定工作目录,请使用绝对路径。如果给出了相对路径,将解释为相对于启动ctest本身的目录,但可能不是构建树的顶部。为了确保工作目录是可预测的,项目应该避免使用相对的WORKING_DIRECTORY。

如果在运行测试时指定的工作目录不存在,CMake版本3.11和更早版本将不会发出错误消息,仍然会运行测试。CMake 3.12及以后版本将捕获错误并将测试视为失败。不管使用的是什么版本的CMake,确保工作目录存在并具有适当的权限是项目的责任。

由于向后兼容性,还支持简化形式的add_test()命令:

add_test(testName command [args...])

这种形式不应该在新项目中使用,因为它缺少全名和命令形式的一些特性。主要区别是生成器表达式不支持,如果command是目标的名称,CMake将不会自动替换其二进制文件的位置。

要运行测试,使用ctest命令行工具,通常从构建目录的顶部运行。当不带命令行参数运行时,将执行一个已定义的测试,并在每个测试启动和完成时记录状态,但隐藏所有测试输出。测试的输出将在最后打印出来:

Test project /path/to/build/dir
 Start 1: fooWithBar
1/2 Test #1: fooWithBar.............. Passed 0.00 sec
 Start 2: fooWithoutBar
2/2 Test #2: fooWithoutBar........... Passed 0.00 sec
100% tests passed, 0 tests failed out of 2
Total Test time (real) = 0.02 sec

如果使用Xcode或Visual Studio这样的多配置生成器,ctest需要了解应该测试哪个配置。这通过-C configType选项来实现,其中configType将是一种受支持的构建类型(Debug、Release等)。对于单配置生成器,-C选项不是必须的,因为构建只能生成一种配置。然而,指定一个配置避免那些定义为仅在特定配置下运行,且空字符串不在列出的配置中运行的测试,仍然是有用的。

可以让ctest用-V选项显示所有测试输出和关于运行的各种其他细节。-VV和-VVV显示出的信息越冗余,但通常只有在ctest上工作的开发人员才需要它们。即使是-V级别的信息,通常也比用户希望看到的更详细,更有可能的是,只有失败的测试的输出才是用户感兴趣的。通过--output-on-failure选项,可以告诉ctest只显示失败测试的输出。或者,开发人员可以将CTEST_OUTPUT_ON_FAILURE环境变量设置为任意值,以避免每次都必须指定它(该值没使用时,ctest只检查是否设置了CTEST_OUTPUT_ON_FAILURE)。

默认情况下,每个测试将在与ctest命令相同的环境中运行。如果测试需要更改环境,可以通过ENVIRONMENT测试属性来完成。此属性预期为NAME=VALUE项的列表,用于定义在运行测试之前要设置的环境变量。更改仅针对该测试,不影响其他测试。

set_tests_properties(fooWithoutBar PROPERTIES
 ENVIRONMENT "FOO=bar;HAVE_BAZ=1"
)

环境变量需要修改,而不是替换现有值的情况就不那么简单了。如果环境应该基于运行CMake的环境而不是ctest命令,那么$ENV{SOMEVAR}可以用于获取现有值。一个很好的例子就是在Windows上增加PATH环境变量,以确保测试可以找到需要链接的动态库:

# In this example, algo is assumed to be a shared library defined elsewhere
# in the project and whose binary will be in a different directory to fooTest
add_executable(fooTest ...)
target_link_libraries(fooTest PRIVATE algo)
add_test(NAME fooWithAlgo COMMAND fooTest)
if(WIN32)
 set_tests_properties(fooWithAlgo PROPERTIES ENVIRONMENT
 "PATH=$<SHELL_PATH:$<TARGET_FILE_DIR:algo>>$<SEMICOLON>$ENV{PATH}"
 )
endif()

用于调用ctest的实际环境修改环境更为复杂,通常也不是严格要求的。可以通过cmake -E env调用一个脚本的组合来实现,cmake -E env将CMake提供的位置作为变量传递给cmake -E env,然后该脚本使用这些值,调用测试可执行文件来执行扩充运行时环境的任务。这样的方法是复杂的,也很脆弱,除非没办法避免,否则应该避免这样使用。

为了方便IDE,启用测试时,CMake定义了一个自定义构建目标,该目标使用一组默认参数调用ctest。对于像Xcode和Visual Studio这样的多配置生成器,这个目标将称为RUN_TESTS,将通过当前选择的构建类型作为ctest的配置。对于单配置生成器,目标简单地称为test,并且在调用ctest时不指定任何配置。在使用RUN_TESTS或测试构建目标时,没有工具指定执行哪些测试,也没有任何其他要传递给ctest的自定义选项。

24.2. 通过/失败标准和其他结果

根据测试命令的退出代码来确定测试结果,可能会有很大的限制,一种的替代方法是指定正则表达式来匹配测试输出。PASS_REGULAR_EXPRESSION测试属性可用于指定一个正则表达式列表,测试输出至少匹配其中一个才能通过测试。这些正则表达式经常跨多行。类似地,FAIL_REGULAR_EXPRESSION测试属性可以设置为一个正则表达式列表。如果其中任何一个匹配测试输出,则测试将失败,即使输出也匹配PASS_REGULAR_EXPRESSION或退出代码为0。测试可以同时拥有PASS_REGULAR_EXPRESSION和FAIL_REGULAR_EXPRESSION集。如果设置了PASS_REGULAR_EXPRESSION且不是空的,则在确定测试通过或失败时不会考虑退出码。

# Ignore exit code, check output to determine the pass/fail status
set_tests_properties(fooTest PROPERTIES
PASS_REGULAR_EXPRESSION
"Checking some condition for fooTest: passed
+.*
All checks passed"
FAIL_REGULAR_EXPRESSION "warning|Warning|WARNING"
)

有时需要跳过某个测试,可能只有测试本身可以确定。可以将SKIP_RETURN_CODE测试属性设置为测试返回的值,以指示它是跳过而不是失败。使用SKIP_RETURN_CODE退出的测试将覆盖其他通过/失败标准。

fooTest.cpp

int main(int argc, char* argv[])
{
 if (shouldSkip())
     return 2; // Skipped

 if (runTest()) 
     return 0; // Passed

 return 1; // Failed
}

CMakeLists.txt

add_executable(fooTest fooTest.cpp ...)
add_test(NAME foo COMMAND fooTest)

set_tests_properties(foo PROPERTIES
 SKIP_RETURN_CODE 2
)

以上测试的输出:

Test project /path/to/build/dir
 Start 1: foo
1/1 Test #1: foo ....................***Skipped 0.00 sec

100% tests passed, 0 tests failed out of 1

Total Test time (real) = 0.01 sec

The following tests did not run:
 1 - foo (Skipped)

当有一个测试失败或由于某种原因没有运行时,将在末尾打印所有这些测试的状态摘要。通过返回代码指示跳过的测试不会认为是失败的,但仍然计算在测试总数中。一个测试可能会因为其他原因而跳过,比如测试依赖项没有满足。

在CMake 3.9或更高版本中,还支持禁用的测试属性。这可用于将测试标记为临时禁用,这将允许对其进行定义,但不执行,甚至不计入测试总数。它不会认为是测试失败,但仍然会在测试结果中显示,并带有适当的状态消息。这类测试通常不应该在很长时间内保持禁用状态,该特性是用来临时禁用有问题的或不完整的测试,直到问题修复为止。

下面的简单示例演示了禁用测试的行为:

add_test(NAME fooWithBar ...)
add_test(NAME fooWithoutBar ...)
set_tests_properties(fooWithoutBar PROPERTIES DISABLED YES)

上面的ctest输出:

Test project /path/to/build/dir
 Start 1: fooWithBar
1/2 Test #1: fooWithBar .............. Passed 0.00 sec
 Start 2: fooWithoutBar
2/2 Test #2: fooWithoutBar ...........***Not Run (Disabled) 0.00 sec

100% tests passed, 0 tests failed out of 1

Total Test time (real) = 0.01 sec

The following tests did not run:
 2 - fooWithoutBar (Disabled)

某些情况下,测试可能会失败。与禁用测试相比,将测试标记为预期失败可能更合适,这样它就可以继续执行。可以将WILL_FAIL测试属性设置为true来表示这一点。这样做的另一个好处是,如果测试开始通过了,ctest会认为这个测试失败了,开发人员会立即意识到行为上的变化。

测试通过/失败状态的另一个方面是完成时间。如果设置了TIMEOUT测试属性,则指定在终止测试并标记为失败之前,允许测试运行的秒数。ctest命令行还接受一个--timeout选项,对任何没有设置TIMEOUT属性的测试都具有相同的效果。此外,还可以通过为ctest指定--stoptime选项,将时间限制应用于整个测试集。停止时间之后的参数必须是实时时间,而不是秒数,如果没有给出时区,则假定为本地时间。

add_test(NAME t1 COMMAND ...)
add_test(NAME t2 COMMAND ...)
set_tests_properties(t2 PROPERTIES TIMEOUT 10)
ctest --timeout 30 --stop-time 13:00

上面的示例中,ctest命令行上,每个测试的默认超时设置为30秒。因为t1没有设置TIMEOUT属性,所以它的超时时间为30秒,而t2的TIMEOUT属性设置为10,这将覆盖ctest命令行上的默认设置。测试将在当地时间下午1点前完成。

某些情况下,测试可能需要等待特定的条件满足,才开始测试。满足该条件并开始真正的测试之后,可能需要对运行的部分应用超时。CMake 3.6或更高版本中,TIMEOUT_AFTER_MATCH测试属性可用来支持这种行为。需要一个包含两个条目的列表,第一个是满足条件后作为超时使用的秒数,第二个是一个正则表达式,用于匹配测试输出。找到正则表达式后,将重置测试的超时倒计时和开始时间,并将超时值设置为第一个列表项。例如,下面将对测试应用30秒的超时,但是一旦字符串条件在测试输出中出现,测试将有10秒的时间来完成,而原来的30秒超时条件将不再适用。

set_tests_properties(t2 PROPERTIES
 TIMEOUT 30
 TIMEOUT_AFTER_MATCH "10;Condition met"
)

如果测试需要25秒来满足条件,那么测试的总时间可能长达35秒,但是因为测试的开始时间进行了重置,所以ctest将报告0到10秒之间的时间(即满足条件所花费的时间不统计)。另一方面,如果在30秒内没有满足条件,测试将总体测试时间显示为30秒。

可能的情况下,通常应该避免使用TIMEOUT_AFTER_MATCH,以便使用其他方法处理前置条件。第24.5节“测试依赖”和第24.4节“并行执行”将讨论更好的替代方案。

24.3. 测试分组与选择

较大的项目中,运行测试子集很常见。开发人员可能只关注一个特定的失败测试,而对所有其他测试不感兴趣。只执行测试的特定子集的一种方法是给ctest提供-R和-E选项。这些选项每个都指定一个正则表达式来匹配测试名称。-R选择要包含在测试集中的测试,而-E则排除测试。可以指定这两个选项,组合它们的效果。

add_test(NAME fooOnly COMMAND ...)
add_test(NAME barOnly COMMAND ...)
add_test(NAME fooWithBar COMMAND ...)
add_test(NAME fooSpecial COMMAND ...)
add_test(NAME other_foo COMMAND ...)
ctest -R Only # Run just fooOnly and barOnly
ctest -E Bar # Run all but fooWithBar
ctest -R '^foo' -E fooSpecial # Run all tests starting with foo except fooSpecial
ctest -R 'fooSpecial|other_foo' # Run only fooSpecial and other_foo

有时,编写正则表达式来捕获所需的测试并不是那么容易,或者开发人员可能只想查看所有测试而不运行它们。-N选项只打印测试而不是运行它们,这可以用来检查正则表达式是否生成所需的测试集的方法。

ctest -N

Test project /path/to/build/dir
 Test #1: fooOnly
 Test #2: barOnly
 Test #3: fooWithBar
 Test #4: fooSpecial
 Test #5: other_foo

Total Tests: 5
ctest -N -R 'fooSpecial|other_foo'

Test project /path/to/build/dir
Test #4: fooSpecial
Test #5: other_foo

Total Tests: 2

添加测试时,会给定一个测试号,这个测试号在运行时保持不变,除非在项目中添加或删除另一个测试。当使用-N选项时,将按照项目定义的顺序列出测试,但不一定按照该顺序执行测试。可以通过测试号,而不是使用-I选项的名称来选择要运行的测试。此方法相当脆弱,因为添加或删除单个测试可能会改变分配给任意数量的其他测试的数量。即使通过-C选项将不同的配置传递给ctest,也会导致测试编号的更改。大多数情况下,最好按名称匹配。

测试号可能有用的情况是,两个测试的名称完全相同。如果在同一目录中定义,两个测试都会运行,不会发出任何警告。虽然重复的测试名称通该避免,但在涉及外部提供的分层测试项目中,总会碰到这样的情况。

-I选项期望参数具有某种复杂的形式。最直接的形式是在命令行上指定测试号,用逗号分隔,不带空格:

ctest -I [start[,end[,stride[,testNum[,testNum...]]]]]

要指定单独的测试号,开始、结束和跨越可以保持空白,如下所示:

ctest -I ,,,3,2 # Selects tests 2 and 3 only

可以从文件中读取相同的细节,而不必在命令行中通过将文件的名称指定给-I选项来指定。如果运行相同复杂的测试集,并且没有添加或删除测试,这就非常有用:

testNumbers.txt

,,,3,2
ctest -I testNumbers.txt

如果需要执行大量的相关测试,按名称或编号单独选择测试就会变得很麻烦。可以使用LABELS测试属性为测试分配任意标签列表,然后可以根据标签选择测试。-L和-LE选项类似于-R和-E选项,只是操作的是测试标签而不是测试名称。继续前面的示例,定义相同的测试:

set_tests_properties(fooOnly PROPERTIES LABELS "foo")
set_tests_properties(barOnly PROPERTIES LABELS "bar")
set_tests_properties(fooWithBar PROPERTIES LABELS "foo;bar;multi")
set_tests_properties(fooSpecial PROPERTIES LABELS "foo")
set_tests_properties(other_foo PROPERTIES LABELS "foo")
ctest -L bar

Test project /path/to/build/dir
 Start 2: barOnly
1/2 Test #2: barOnly .......................... Passed 1.52 sec
 Start 3: fooWithBar
2/2 Test #3: fooWithBar ....................... Passed 1.02 sec

100% tests passed, 0 tests failed out of 2

Label Time Summary:
bar = 2.53 sec*proc (2 tests)
foo = 1.02 sec*proc (1 test)
multi = 1.02 sec*proc (1 test)

Total Test time (real) = 2.54 sec

标签不仅可以方便地对测试进行分组,还可以对基本的执行时间统计进行分组。正如在上面的示例输出,当执行的测试集中的任何测试都设置了LABELS属性时,ctest命令会打印标签信息。这允许开发人员了解每个标签组占整个测试时间的情况。sec*proc单元的proc部分是指分配给测试的处理器数量(24.4节中描述,下面的“并行执行”)。运行3秒并需要4个处理器的测试,报告值为12。可以使用--no-label-summary选项禁用标签时间摘要。

另一个常见的是重新运行上次运行ctest时失败的测试。这是一种方便的方法,可以在进行小的修复后重新检查相关的测试,或者重新运行失败的测试。ctest命令支持--rerun-failed选项,该选项提供这种行为,不需要给出任何测试名称、数字或标签。

有时,某个测试或一组测试只是间歇性地失败,因此可能需要运行多次测试,以尝试重现失败。与反复运行ctest本身不同,可以使用--repeat-until-fail选项提供每个测试可以重复的次数上限。如果测试失败,ctest将不会再重新运行这个测试。

ctest -L bar --repeat-until-fail 3

Test project /path/to/build/dir
 Start 2: barOnly
 Test #2: barOnly .......................... Passed 1.52 sec
 Start 2: barOnly
 Test #2: barOnly ..........................***Failed 0.00 sec
 Start 3: fooWithBar
 Test #3: fooWithBar ....................... Passed 1.02 sec
 Start 3: fooWithBar
 Test #3: fooWithBar ....................... Passed 1.02 sec
 Start 3: fooWithBar
2/2 Test #3: fooWithBar ....................... Passed 1.02 sec

50% tests passed, 1 tests failed out of 2

Label Time Summary:
bar = 1.02 sec*proc (2 tests)
foo = 1.02 sec*proc (1 test)
multi = 1.02 sec*proc (1 test)

Total Test time (real) = 4.59 sec

The following tests FAILED:
 2 - barOnly (Failed)
Errors while running CTest

标签摘要不会累积重复测试的总时间,只使用测试最后一次执行的时间。但是,总测试时间会计算所有重复的次数。

24.4. 并行执行

对于大型项目或者测试需要大量时间来完成的项目来说,最大化测试吞吐量是一个重要的考虑因素。并行运行测试的能力是ctest的关键特性,可以使用与标准make工具非常相似的命令行选项来启用。可以使用-j选项指定同时运行的测试数量的上限。与大多数make实现不同的是,必须提供一个值,否则选项将不起作用。另一种方法是,CTEST_PARALLEL_LEVEL环境变量可以用来指定作业的数量,但如果同时使用这两个变量,则命令行选项优先。这种安排对于持续集成构建服务器特别有用,因为CTEST_PARALLEL_LEVEL可以设置为每个服务器上的CPU核数,从而使每个项目不必自己计算并行任务的数量。对于那些需要限制并行作业数量的项目,仍然可以使用-j命令行选项覆盖CTEST_PARALLEL_LEVEL。

有一个相关的选项是-l,它用于指定CPU负载的上限。如果负载超过此限制,ctest将尽量停止新的测试。不过,这个选项的缺点在测试开始时就很明显。通常,ctest最初会启动-j或CTEST_PARALLEL_LEVEL设置所允许的任务限制范围内的测试,超过-l指定的任何限制。测量的CPU负载通常有延迟,这允许ctest在测量负载增加之前开始太多的测试。为了防止这种情况发生,应该将-j或CTEST_PARALLEL_LEVEL指定的并行任务数量设置为不超过-l的限制。如果既没有设置-j,也没有设置CTEST_PARALLEL_LEVEL,-l选项将不起作用。尽管存在这些限制,但-l选项在帮助减少共享系统上的CPU过载时仍然有用,因为其他进程可能也在竞争CPU资源。

默认情况下,ctest假定每个测试消耗一个CPU。对于使用多个CPU的测试用例,可以设置它们的PROCESSORS测试属性,以表示预期使用多少个CPU。开始测试之前,ctest将使用该值来确定是否有足够的CPU资源可用。如果PROCESSORS设置为高于任务限制的值,在确定是否可以启动测试时,ctest将表现为任务的限制。

这些选项的效果可以在下面的示例输出中看到,这些输出使用与前面定义的测试集相同。

ctest -j 5

Test project /path/to/build/dir
 Start 5: other_foo
 Start 2: barOnly
 Start 3: fooWithBar
 Start 1: fooOnly
 Start 4: fooSpecial
1/5 Test #4: fooSpecial ....................... Passed 0.12 sec
2/5 Test #1: fooOnly .......................... Passed 0.52 sec
3/5 Test #3: fooWithBar ....................... Passed 1.01 sec
4/5 Test #2: barOnly .......................... Passed 1.52 sec
5/5 Test #5: other_foo ........................ Passed 2.02 sec
100% tests passed, 0 tests failed out of 5

Label Time Summary:
bar = 2.53 sec*proc (2 tests)
foo = 1.65 sec*proc (3 tests)
multi = 1.01 sec*proc (1 test)

Total Test time (real) = 2.03 sec

定义了5个测试,命令行上的任务限制为5,因此ctest能够立即启动所有测试。每个测试的结果在完成时记录,而不是以它们的启动顺序。将任务限制减少到2显示如下输出:

ctest -j 2

Test project /path/to/build/dir
 Start 5: other_foo
 Start 2: barOnly
1/5 Test #2: barOnly .......................... Passed 1.52 sec
 Start 3: fooWithBar
2/5 Test #5: other_foo ........................ Passed 2.01 sec
 Start 1: fooOnly
3/5 Test #1: fooOnly .......................... Passed 0.52 sec
 Start 4: fooSpecial
4/5 Test #3: fooWithBar ....................... Passed 1.02 sec
5/5 Test #4: fooSpecial ....................... Passed 0.12 sec

100% tests passed, 0 tests failed out of 5

Label Time Summary:
bar = 2.54 sec*proc (2 tests)
foo = 1.65 sec*proc (3 tests)
multi = 1.02 sec*proc (1 test)

Total Test time (real) = 2.66 sec

对于大量的测试和较高的任务限制,很难跟踪记录每个单独测试的情况。在运行结束时的总体测试总结变得更加重要,因为没有通过的每个测试都与其结果一起列出了。

测试有时需要确保没有其他测试与它们并行运行。它们可能正在执行对机器上的其他活动敏感的操作,或者可能会干扰其他测试的条件。要实施此约束,可以将测试的RUN_SERIAL属性设置为true。这是一个相当严格的约束,可能对测试吞吐量有很大的影响,所以应该有节制地使用。通常,更好的替代方法是使用RESOURCE_LOCK测试属性,用于提供测试需要独占访问的资源列表。这些资源是任意的字符串,ctest不以任何方式解释它们,除非确保RESOURCE_LOCK属性中列出任何这些资源,其他测试不会同时使用。这种方式可以解决需要独占访问某些东西(例如数据库、共享内存)的测试,而不会阻塞与该资源不相关的测试。

set_tests_properties(fooOnly fooSpecial other_foo PROPERTIES RESOURCE_LOCK foo)
set_tests_properties(barOnly PROPERTIES RESOURCE_LOCK bar)
set_tests_properties(fooWithBar PROPERTIES RESOURCE_LOCK "foo;bar")

下面的示例输出显示,尽管作业限制为5允许同时执行所有测试,但ctest延迟启动了某些测试,直到所需的资源可用为止。

ctest -j 5

Test project /path/to/build/dir
 Start 5: other_foo
 Start 2: barOnly
1/5 Test #2: barOnly .......................... Passed 1.52 sec
2/5 Test #5: other_foo ........................ Passed 2.02 sec
 Start 3: fooWithBar
3/5 Test #3: fooWithBar ....................... Passed 1.01 sec
 Start 1: fooOnly
4/5 Test #1: fooOnly .......................... Passed 0.52 sec
 Start 4: fooSpecial
5/5 Test #4: fooSpecial ....................... Passed 0.12 sec

100% tests passed, 0 tests failed out of 5

Label Time Summary:
bar = 2.53 sec*proc (2 tests)
foo = 1.65 sec*proc (3 tests)
multi = 1.01 sec*proc (1 test)

Total Test time (real) = 3.67 sec

24.5. 测试依赖

测试不仅可用于验证特定条件,还可用于执行这些条件。例如,测试可能需要连接服务器,以便验证客户机实现。不必依赖开发人员来确保这样的服务器可用,可以创建另一个测试用例来确保服务器正在运行。然后,客户端测试需要对服务器具有某种依赖性,以确保能以正确的顺序运行。

DEPENDS测试属性允许通过在测试运行前完成的其他测试列表来表示约束的形式。以上客户机/服务器示例可以简单地表示为:

set_tests_properties(clientTest1 clientTest2 PROPERTIES DEPENDS startServer)
set_tests_properties(stopServer PROPERTIES DEPENDS "clientTest1;clientTest2")

DEPENDS测试属性的缺点是,定义测试顺序时,不考虑先决测试是通过还是失败。上面的示例中,如果startServer测试用例失败,仍然会运行clientTest1、clientTest2和stopServer测试。这些测试很可能会失败,测试输出将显示所有四个测试都失败,而实际上只有startServer测试失败,其他测试应该跳过。

CMake 3.7增加了对测试固件的支持,这个概念允许更严格地表达测试之间的依赖关系。测试可以通过FIXTURES_REQUIRED测试属性中列出该固件的名称,来表示它需要某个特定的固件。在FIXTURES_SETUP测试属性中具有相同固件名称的任何其他测试必须成功完成,然后才会启动相关测试。如果某个固件的设置测试失败,则需要该固件的所有测试都标记为跳过。类似地,测试可以在FIXTURES_CLEANUP测试属性中列出一个固件,以指示它必须在任何其他测试之后运行,该测试在其FIXTURES_SETUP或FIXTURES_REQUIRED属性中列出相同的固件。这些清理测试不需要通过设置或修复程序的测试,因为即使早期测试失败,也可能需要清理。

所有三个与固件相关的测试属性都接受固件名称列表。这些名称是任意的,不必与它们所使用的测试名称、资源或任何其他属性相关。固件名称应该能让开发人员清楚它们表示什么,因此它们通常与RESOURCE_LOCK属性使用的值相同。

考虑前面的客户机/服务器示例。这可以用具有以下属性的固件表示:

set_tests_properties(startServer PROPERTIES FIXTURES_SETUP server)
set_tests_properties(clientTest1 clientTest2 PROPERTIES FIXTURES_REQUIRED server)
set_tests_properties(stopServer PROPERTIES FIXTURES_CLEANUP server)

上面的例子中,server是固件的名称,clientTest1和clientTest2会在startServer后stopServer前运行。如果启用了并行执行,startServer将首先运行,两个客户机测试将同时运行,而stopServer将只在两个客户机测试都完成或跳过之后运行。

固件的另一个好处是,可以让开发人员只运行测试的一个子集时看到。考虑这样一个场景:开发人员正在处理clientTest2,但对运行clientTest1不感兴趣。当使用DEPENDS表示测试之间的依赖关系时,开发人员负责确保在测试集中包括所需的测试,这需要理解所有相关的依赖关系。这样ctest的命令行为:

ctest -R "startServer|clientTest2|stopServer"

使用固件时,ctest会自动将任何设置或清理测试添加到要执行的测试集,以满足固件需求。这意味着开发者只需要指定他们想要关注的测试,而把依赖关系交给ctest处理:

ctest -R clientTest2

使用--rerun-failed选项时,相同的机制确保将设置和清理测试自动添加到测试集中,以满足之前失败测试的固件依赖关系。

一个固件可以有零个或多个安装测试和零个或多个清理测试。固件可以定义设置测试,而不定义清理测试,反之亦然。虽然不是特别有用,但固件可以不包含设置或清理测试。这种情况下,固件对要执行的测试或测试将运行的时间没有影响。类似地,固件可以有与之关联的设置和/或清理测试,但不进行测试。开发过程中定义测试或禁用测试时,可能会出现这些情况。对于不需要测试的情况,CMake 3.7中的bug允许该固件的清理测试在安装测试之前运行,这个bug在3.8.0版本中得到了修复。

一个更复杂的示例演示了,如何使用固件来表示更复杂的测试依赖关系。扩展前面的示例,假设一个客户机测试只需要一个服务器,而另一个客户机测试需要服务器和数据库都可用。这可以通过定义两个固件来表示:server和database。对于后者,可以简单地检查是否有可用的数据库,如果没有可用的数据库,则失败,因此数据库固件不需要清理测试。服务器和数据库固件不相关,因此不需要依赖关系。这些可以这样表达:

# Setup/cleanup
set_tests_properties(startServer PROPERTIES FIXTURES_SETUP server)
set_tests_properties(stopServer PROPERTIES FIXTURES_CLEANUP server)
set_tests_properties(ensureDbAvailable PROPERTIES FIXTURES_SETUP database)

# Client tests
set_tests_properties(clientNoDb PROPERTIES FIXTURES_REQUIRED server)
set_tests_properties(clientWithDb PROPERTIES FIXTURES_REQUIRED "server;database")

虽然让ctest自动将固件依赖项添加到测试执行集中通常是一个有用的特性,但有时也不希望这样做。继续上面的示例,开发人员可能想让服务器继续运行,并多次执行客户机测试。他们可能正在进行更改,重新编译代码,并检查每次更改是否通过客户机测试。为了支持这种级别的控制,CMake 3.9向ctest引入了-FS、-FC和-FA选项,每个选项都需要一个正则表达式,该正则表达式与固件名称匹配。-FS选项用于禁用为与提供的正则表达式匹配的固件添加依赖项。-FC对清除测试执行相同的操作,而-FA将两者结合,禁用匹配的设置和清除测试。一种常见的情况是完全禁用添加任何安装/清理依赖项,这可以通过给出句点(.)的正则表达式来完成。下面的例子演示了这些选项的效果:

24.6. 交叉编译和模拟器

当项目定义的可执行目标用于add_test()命令时,CMake会自动替换构建可执行文件的位置。对于交叉编译来说,这通常不能起作用,因为主机通常不能直接运行为不同平台的二进制文件。为了实现这一点,CMake提供了CROSSCOMPILING_EMULATOR目标属性,可以将目标设置为启动脚本或可执行文件。如果设置了这个属性,CMake将把它放在目标二进制文件之前,并将其作为命令来运行(也就是说,真正的目标二进制文件将成为CROSSCOMPILING_EMULATOR提供的模拟器命令的第一个参数)。这样,即使在交叉编译时也可以运行测试。

CROSSCOMPILING_EMULATOR不必是实际的模拟器,它只需是可以在主机上运行目标可执行文件的命令。可以在目标平台的模拟器上执行用例,也可以将其设置为脚本,或将可执行文件复制到目标机器并远程运行(例如:通过SSH连接)。无论使用哪种方法,开发人员都应该知道,模拟器或二进制文件的启动时间可能非常重要,可能会对测试计时标准产生影响。反过来,这意味着可能需要修改超时设置。

CROSSCOMPILING_EMULATOR目标属性的默认值取自CMAKE_CROSSCOMPILING_EMULATOR变量,这是获取模拟器详细信息的常用方式,而不是设置每个目标的属性。这个变量通常在工具链中设置,因为它影响try_run()命令的方式,与上面影响测试和定制命令的方式类似。

即使没有交叉编译,CMake仍然会使用一个非空的CROSSCOMPILING_EMULATOR目标属性,并将它前置到命令行中,用于测试和执行该目标的自定义命令。这可能非常有用,允许将属性临时设置为启动脚本,从而可以进行调试或数据收集等工作。不建议将此技术作为项目构建的永久特性,在某些开发情况这种方式很有用。

24.7. 构建和测试模式

ctest不仅可以用来执行一组测试,它还可以驱动整个配置、构建和测试。主要有两种方法:一种最基本、独立的方法,另一种与仪表工具紧密关联的方法。基本的方法是使用--build-and-test命令行选项调用ctest工具,其有自己的预期形式:

ctest --build-and-test sourceDir buildDir
 --build-generator generator
 [options...]
 [--test-command testCommand [args...]]

如果没有任何选项,上面的代码将使用sourceDir和binaryDir运行CMake,并使用指定的生成器。所以这三个选项都必须指定。如果CMake运行成功,ctest将构建clean目标,最后构建默认的all目标。在构建步骤之后运行测试,命令行上的最后一个选项必须是--test-command,及其关联的testCommand和一些可选参数。这是另一中调用ctest来运行所有测试的方式。

ctest --build-and-test sourceDir buildDir
 --build-generator Ninja
 --test-command ctest -j 4

以上命令执行了一个完整的配置-清理-建造-测试流水。提供了各种选项,可用于修改运行管道的哪些部分,以及如何运行。例如,--build-nocmake和--build-noclean分别禁用configure和clean步骤。--build-two-config选项将调用CMake两次,用来处理某些特殊情况,即需要通过第二次CMake来完全配置项目。在使用像Visual Studio这样的生成器时,需要使用--build-generator-platform和--build -generator-toolset指定额外的生成器信息,它们将分别作为-A和-T选项传递给cmake,并用于配置步骤。像Xcode这样的生成器可能需要给定项目名称,以便找到配置阶段生成的项目文件,这可以通过--build-project选项来完成。可以使用--build-target选项设置构建步骤中要构建的目标,并且可以通过使用替代工具传递--build-makeprogram覆盖构建工具。

从上面可以看到,与--build-and-test模式相关的所有选项都以--build开始。虽然大多数选项都有直观的名称,但是--build前缀可能会一些异常情况。存在一个名为--build-options的选项,它最初看起来可能与构建步骤相关,但实际上用于将命令行选项传递给cmake命令。还有一个附加的约束,即必须在命令行的末尾,除非同时指定了--test-command,这种情况下--build-options必须在--test-command之前。下面的示例将展示这些约束。它向cmake调用添加了两个缓存变量定义,并在构建步骤之后运行完整的测试套件。

ctest --build-and-test sourceDir buildDir
 --build-generator Ninja
 --build-options -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON
 --test-command ctest -j 4

还有一些其他的--build-…选项,但上面的内容已经涵盖了最常用的选项。应该提到的另一个选项是--test-timeout,它对test命令运行的时间设置了一个时间限制(以秒为单位)。

使用ctest命令控制整个流水,与显式调用每个阶段所需的每个工具相比,好坏要取决于特定的情况。上面的最后一个例子可以在Unix上使用以下等价的命令:

mkdir -p buildDir
cd buildDir
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON sourceDir
cmake --build . --target clean
cmake --build .
ctest -j 4

单独调用每个工具允许以完整的选项集运行,而ctest --build-and-test方法对于控制构建阶段的能力非常有限。

构建和测试模式特别方便的是,项目需要执行一个完整的配置-构建-测试,从而与主构建分离。由于整个循环可以通过一个ctest来控制,因此可以将它用作对add_test()调用的COMMAND,这使得将项目添加到主项目的测试套件中会变得简单。CMake本身就以这种方式,在自己的测试套件中广泛地使用了ctest构建和测试模式。

下面的例子展示了如何使用一个单独的构建来测试由主项目构建的库提供的API:

add_library(decoder foo.c bar.c)

add_test(NAME decoder.api
 COMMAND ${CMAKE_CTEST_COMMAND}
 --build-and-test ${CMAKE_CURRENT_LIST_DIR}/test_api
 ${CMAKE_CURRENT_BINARY_DIR}/test_api
 --build-generator ${CMAKE_GENERATOR}
 --build-options -DDECODER_LIB=$<TARGET_FILE:decoder>
 --test-command ${CMAKE_CTEST_COMMAND}
)

test_api源目录将包含它自己的CMakeLists.txt文件,其目的是配置一个针对解码器库链接的构建,并在DECODER_LIB中设置的绝对路径(这只是将库位置传递给测试项目的几种方法之一)。关于这种类型的测试,还可以用来验证一个特定的测试项目没有构建,或者验证因一个特定的致命错误导致配置失败(例如一个丢失的符号)。这种预期的构建错误不能在主项目中测试,因为它会导致主项目的构建失败。

此类测试可能有用的场景是测试主项目创建的代码生成器输出。测试固件可用于设置一对测试,一个用于生成代码,另一个用于执行测试构建。如果通过代码生成器创建,那么cmake通常会读取的文件,比如CMakeLists.txt文件。例如:

add_executable(codegen generator.cpp)

add_test(NAME generate_code COMMAND codegen)
add_test(NAME build_generated_code
 COMMAND ${CMAKE_CTEST_COMMAND}
 --build-and-test ${CMAKE_CURRENT_LIST_DIR}/test_generation
 ${CMAKE_CURRENT_BINARY_DIR}/test_generation
 --build-generator ${CMAKE_GENERATOR}
 --test-command ${CMAKE_CTEST_COMMAND}
)

set_tests_properties(generate_code PROPERTIES FIXTURES_SETUP generator)
set_tests_properties(build_generated_code PROPERTIES FIXTURES_REQUIRED generator)

构建和测试模式也可以用于验证CMake程序脚本,方法是将它们包含在一个小型的测试项目中,并在适当的情况下调用其功能。实际上,这提供了一种相当方便的方法来实现CMake脚本的单元测试,从而避免了将此类测试放到主项目的配置阶段。

虽然构建和测试模式对于上面提到的这些情况肯定很有用,但它缺乏完全脚本化运行的灵活性,在这种情况下,每个命令都可以使用完整的选项集。下一节将介绍调用ctest的另一种方法,它提供了对整个流水的更强大处理,包括一些有用的报告功能。

24.8. 集成CDash

CTest与另一款名为CDash的产品有着悠久的历史和密切的合作关系,CDash也是CMake和CTest背后的同一家公司开发的。CDash是一个基于web的仪表板,它从ctest驱动的软件构建和测试管道收集结果。它从管道的每个阶段收集警告和错误,并显示每个阶段的概要信息,并能够单击到每个单独的警告或错误。通过对过去流水的历史记录,可以观察一段时间内的趋势并比较运行情况。CMake本身有自己的仪表盘,跟踪夜间构建,与合并请求相关的构建等等。花几分钟研究一个示例仪表盘将有助于理解本节所涵盖的内容:

https://open.cdash.org/index.php?project=CMake

24.8.1. CDash的重要概念

CTest和CDash如何执行流水和报告结果联系在一起,这里有三个重要的概念:步骤(有时也称为动作)、模型(有时也称为模式)和跟踪。步骤是流水执行的一系列操作。按照它们通常的调用顺序,定义的主要动作集是:

  • Start

  • Update

  • Configure

  • Build

  • Test

  • Coverage

  • MenCheck

  • Submit

并不是所有的操作都必须执行,有些操作可能不受支持或不需要运行。简单地说,CDash仪表盘中的每一行对应一个管道,通常会显示所执行操作的信息(提交哈希、警告、错误、失败等)。

每个流水都必须与一个模型相关联,该模型用于定义某些行为,例如在特定步骤失败后是否继续后续步骤。当没有请求特定操作时,该模型还提供一组默认操作。支持的模型有:

夜间性

每天调用一次,通常在执行机器不太忙的时候由自动化作业。默认的操作集包括上面列出的所有步骤,MemCheck除外。如果Update步骤失败,仍将执行其余步骤。

持续性

与夜间性构建非常相似,不同之处在于在一天中根据需要运行多次,通常是为了响应提交的更改。它定义了与夜间性构建相同的一组操作,但是如果Update步骤失败,则不会执行后面的步骤。

实验性

该模型用于由开发人员根据需要执行特殊的实验。默认操作集包括Update和MemCheck之外的所有步骤。如果指定了三种已定义模型之外的模型类型,或者根本没有指定任何模型类型,则该模型类型将视为实验性的。

跟踪控制在仪表盘结果中会显示相应流水的结果,跟踪名称可以是项目或开发人员希望使用的任何名称,但如果没有指定跟踪,将设置为与模型相同的名称。这会有一种常见的误解,即模型在仪表盘中控制分组,而这是由跟踪完成的。覆盖率和MemCheck操作是一种特殊情况,它们有效地忽略了跟踪,并且它们的仪表盘结果显示在它们自己的专用组中(分别是覆盖率和动态分析)。

24.8.2. 执行管道和操作

对于有配置文件的项目(下一节将介绍),可以使用以下ctest命令形式调用整个流水或单个步骤:

ctest [-M Model] [-T Action] [--track Track] [otherOptions...]

必须指定至少一个或两个Model和Action。为了方便,-M和-T选项可以组合成-D选项,如下所示:

ctest -D Model[Action] [--track Track] [otherOptions...]

-D的参数可以忽略该操作或将其附加到Model中。有效参数例子包括NightlyConfigure,NightlyConfigure,ExperimentalBuild等等。如果需要,可以多次指定-T和-D选项,以便在ctest调用中列出多个步骤。注意-D也可以用于定义ctest变量,ctest命令将处理不能识别的Model或ModelAction作为尝试设置。因此,使用-M和-T选项可能比使用-D更安全。

夜间执行使用默认步骤集,并在默认组中报告结果。夜间执行可以简单写为:

ctest -M Nightly

同样的事情,但是在另一个叫做“夜间管理员”的小组中,结果是这样的:

ctest -M Nightly --track "Nightly Master"

考虑由配置、构建和测试步骤组成的自定义实验流水,并将结果分组在简单测试下。这需要显式地指定步骤集,因为它不同于为实验模型定义的默认操作集(没有执行覆盖步骤)。这可以使用ctest的调用序列来完成,也可以使用一个命令行上的多个-T选项来完成。两种形式如下所示:

# Separate commands
ctest -T Start -M Experimental --track "Simple Tests"
ctest -T Configure
ctest -T Build
ctest -T Test
ctest -T Submit

# One command
ctest -M Experimental --track "Simple Tests" \
 -T Start -T Configure -T Build -T Test -T Submit

第一步应该是一个Start操作,用于初始化流水细节,并记录后续步骤将使用的模型和跟踪名称。如果将每个操作拆分为单独的ctest调用,那么对于后面的任何步骤,都不需要重复这些细节。最后一步是提交操作,这里假设目标是向仪表盘提交最终的结果。

上面的所有输出都收集在调用ctest目录下的一个测试子目录中。Start操作写出一个名为TAG的文件,该文件至少包含两行,第一行是运行开始的日期-时间,格式为YYYYMMDD-hhmm,第二行是跟踪名称。CMake 3.12将模型名称作为第三行。Start后的每一步行动,它将创建自己的输出文件在Testing/YYYYMMDD-hhmm/<Action>.xml中,并将测试日志记录在Testing/Temporary/Last<Action>_YYYYMMDD-hhmm.log文件中 (在MemCheck步骤的情况下,部分将是动态分析,而不是这些文件名中的MemCheck)。Submit操作收集XML输出文件和一些日志文件,并将它们提交到指定的指示板上。

要将构建注释附加到整个流水中,请在提交步骤中使用-A或--add-notes选项来指定要上传的文件名,如果要添加多个文件,可以用分号分隔。这可能是记录有关特定流水的额外细节的方法,例如来自持续集成系统的信息。

ctest -T Submit --add-note JobNote.txt

另外还有一个--extra-submit选项,主要用于ctest内部使用。它不是通用的文件上传机制,开发人员或项目不应该直接使用。

虽然上述功能主要用于与CDash集成,也可以用于其他场景。例如,Jenkins CI系统有一个插件,允许它读取测试操作的Test.xml输出文件,并以类似于CDash的方式记录测试结果。与普通方式运行ctest不同,它可以仅使用Test操作的仪表盘。Jenkins插件只需要知道在哪里可以找到Test .xml文件,就可以读取测试结果。使用这种方式时,启动操作都可以省略,因为如果其他步骤没有在启动操作前执行,ctest将使用Experimental模式,静默地执行启动操作。项目可能希望在这样做之前清除测试目录中的内容,以确保Jenkins只获取当前运行的结果。

当将XML输出文件传递给CDash以外的工具时,可能需要ctest不要压缩输出。默认情况下,操作的输出压缩成ascii编码的形式写入XML文件,可以通过--no-compress-output选项传递给ctest来避免这一点。只有在必要时才使用此选项,因为这将会产生更大的输出文件。

仪表盘步骤在没有CDash的情况下,可以支持对代码覆盖或内存的检查(Valgrind, Purify,各种杀毒软件等等)。这些指示盘操作可以使相关工具的使用和结果收集变得更容易。有关如何设置和使用这些工具的详细信息,请参阅下一节。

24.8.3. 配置CTest

为CDash集成准备一个项目,主要是由CMake提供的CTest模块完成。这个模块应该包含在project()命令之后的顶层CMakeLists.txt文件中。

cmake_minimum_required(VERSION 3.0)
project(CDashExample)

# ... set any variables to customize CTest behavior

include(CTest)

# ... Define targets and tests as usual

因为CTest模块会在构建目录中写入各种文件,所以需要包含在顶层CMakeLists.txt文件中,而那些生成的文件通常位于构建树的顶层。如果该项目通过add_subdirectory()合并到父项目中,父项目还应该将include(CTest)放在其顶层CMakeLists.txt中,以便在正确的位置生成必要的文件。

CTest模块定义了BUILD_TESTING缓存变量,默认值为true。用于决定模块是否调用enable_testing(),因此项目不必自己也调用enable_testing()。只有在启用测试时,项目可以使用此缓存变量来执行某些处理。如果项目有很多测试,这些测试需要很长时间来构建,这个变量可以避免测试在不需要时添加到构建中。

cmake_minimum_required(VERSION 3.0)
project(CDashExample)
include(CTest)

# ... define regular targets

if(BUILD_TESTING)
 # ... define test targets and add tests
endif()

CTest模块为每个Model和ModelAction组合定义构建目标。这些目标在执行ctest时,可用-D选项设置目标名称,目的是作为在IDE中执行整个流水或仅执行仪表盘操作。如果使用命令行方式,那么直接用ctest就好了。

CTest模块执行时,非常重要的任务会写在构建目录中名为DartConfiguration.tcl的配置文件中。该文件的名称很有历史性,Dart是CDash最开始的名称。该文件记录很多细节信息,如源目录和构建目录位置、执行构建设备的信息、使用的工具链、各种工具的位置和其他默认值。还包含CDash服务器的详细信息,是为了做到这一点,项目需要在源文件树顶层提供一个CTestConfig.cmake文件,并且带有相关内容。合适的CTestConfig.cmake文件可以从CDash中获得(需要管理员权限),手动创建这个文件也并不困难。看起来像这样:

# Name used by CDash to refer to the project
set(CTEST_PROJECT_NAME "MyProject")

# Time to use for the start of each day. Used by
# CDash to group results by day, usually set to
# midnight in the local timezone of the CDash server.
set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC")

# Details of the CDash server to submit to
set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION "/submit.php?project=${CTEST_PROJECT_NAME}")
set(CTEST_DROP_SITE_CDASH YES)

# Optional, but recommended so that command lines
# can be seen in the CDash logs
set(CTEST_USE_LAUNCHERS YES)

DartConfiguration.tcl文件由CTest模块输出,包含对每个仪表盘的可配置选项。默认情况下,大多数已经设置为适当的值,但是Coverage和MemCheck步骤中,有些项开发人员可能特别感兴趣。这可通过CMake变量控制,开发者可以在CMake缓存中检查和修改这些变量,也可以在包含CTest模块之前在CMakeLists.txt文件中直接设置这些变量。

假设测试覆盖率步骤调用了gcov,CTest模块将通过名字所有命令。COVERAGE_COMMAND缓存变量保存了该搜索的结果,开发人员可以修改它。第二个缓存变量COVERAGE_EXTRA_FLAGS用于COVERAGE_COMMAND的参数,因此开发人员能够控制使用的命令和传递的参数。

MemCheck步骤更有趣。支持许多不同的内存检查工具,包括Valgrind, Purify, BoundsChecker等等。对于前三个,可以通过将MEMORYCHECK_COMMAND设置可执行文件的位置来进行选择。然后,ctest将从可执行文件名称中进行识别。对于Valgrind,还可以设置VALGRIND_COMMAND_OPTIONS变量来覆盖Valgrind原始的选项。要使用其中一个工具,请将MEMORYCHECK_TYPE设置为以下字符串之一(会忽略MEMORYCHECK_COMMAND):

  • AddressSanitizer

  • LeakSanitizer

  • MemorySanitizer

  • ThreadSanitizer

  • UndefinedBehaviorSanitizer

然后ctest会启动测试可执行文件,但相关的环境变量会设置为启用的软件。注意,软件工具需要使用相关的编译器和链接器标志(通常是-fsanitize=XXX,可能还有-fno-omit-frame-pointer)对目标项目进行构建。有关相关标志和工具的详细信息,请参阅Clang或GCC文档。

以上细节足够执行各种仪表盘操作,并将结果提交到CDash服务器,但是存在一个先有鸡还是先有蛋的问题。更新和配置步骤需要执行,以获DartConfiguration.tcl文件。因此,无法捕获这两步的详细信息,对于配置步骤,第一次运行cmake的输出将丢失,只能通过在已经配置的构建目录中重新运行cmake获得输出。不过,其他步骤的输出是可以获取的,在某些情况下这可能就足够了。例如,使用像Gitlab CI或Jenkins这样的持续集成系统时,源码的克隆或更新可以由CI系统本身来处理。可以执行初始的cmake运行,然后其他步骤可以作为仪表盘操作运行。最终结果可以提交给CDash服务器,也可以由CI系统直接读取,或者两者都可以。

为了捕获完整的输出,包括现有源码树的克隆或更新,以及第一个配置步骤,必须编写自定义ctest脚本来建立所有必需的设置细节,并调用相关的ctest函数。这可能是非常复杂的过程,如果已经在使用另一个CI系统,就不需要这样做。如果不需要捕获克隆/更新步骤,定制脚本的复杂性就会降低。以这种方式使用时,ctest使用-S或-SP选项调用(它们是相同的,只是后者创建一个新进程,而前者不会)。下面演示了一个相当简单的示例。

ctest -S MyCustomCTestJob.cmake

MyCustomCTestJob.cmake

# Re-use CDash server details we already have
include(${CTEST_SCRIPT_DIRECTORY}/CTestConfig.cmake)

# Basic information every run should set, values here are just examples
site_name(CTEST_SITE)
set(CTEST_BUILD_NAME ${CMAKE_HOST_SYSTEM_NAME})
set(CTEST_SOURCE_DIRECTORY "${CTEST_SCRIPT_DIRECTORY}")
set(CTEST_BINARY_DIRECTORY "${CTEST_SCRIPT_DIRECTORY}/build")
set(CTEST_CMAKE_GENERATOR Ninja)
set(CTEST_CONFIGURATION_TYPE RelWithDebInfo)

# Dashboard actions to execute, always clearing the build directory first
ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY})
ctest_start(Experimental)
ctest_configure()
ctest_build()
ctest_test()
ctest_submit()

虽然上面的自定义脚本相当简单,但下面的示例更有趣,说明了自定义脚本是如何定义行的。不是等到运行的最后才将结果提交到仪表盘,而是在每个步骤之后逐步提交(如果某些步骤花费了很长时间,这很有用)。这些可执行文件是工具构建的,通过运行工具的检查,不是常规测试。最后还上传了一些额外的文件。

include(${CTEST_SCRIPT_DIRECTORY}/CTestConfig.cmake)

site_name(CTEST_SITE)
set(CTEST_BUILD_NAME "${CMAKE_HOST_SYSTEM_NAME}-ASan")
set(CTEST_SOURCE_DIRECTORY "${CTEST_SCRIPT_DIRECTORY}")
set(CTEST_BINARY_DIRECTORY "${CTEST_SCRIPT_DIRECTORY}/build")
set(CTEST_CMAKE_GENERATOR Ninja)
set(CTEST_CONFIGURATION_TYPE RelWithDebInfo)
set(CTEST_MEMORYCHECK_TYPE AddressSanitizer)
set(configureOpts
 "-DCMAKE_CXX_FLAGS_INIT=-fsanitize=address -fno-omit-frame-pointer"
 "-DCMAKE_EXE_LINKER_FLAGS_INIT=-fsanitize=address -fno-omit-frame-pointer"
)
ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY})
ctest_start(Experimental TRACK Sanitizers)
ctest_configure(OPTIONS "${configureOpts}")
ctest_submit(PARTS Start Configure)
ctest_build()
ctest_submit(PARTS Build)
ctest_memcheck()
ctest_submit(PARTS MemCheck)
ctest_upload(FILES ${CTEST_BINARY_DIRECTORY}/mytest.log
 ${CTEST_BINARY_DIRECTORY}/anotherFile.txt
)
ctest_submit(PARTS Upload Submit)

CMake文档中详细介绍了各种ctest_…命令,以及CTest和CMake变量,这些变量可用于定制每个步骤或方法。上面的脚本是一个基础脚本,可以用来试验不同的参数和变量。

同时处理克隆/更新项目的脚本会比较复杂。项目通常有特殊的方法来实现这一点,通常需要决定应该如何安排夜间和持续构建之类的事情。对合并请求的自动化构建等支持,将在很大程度上依赖于承载项目存储库的功能。对于那些感兴趣的人,推荐的入门方法是找到一个使用类似存储库托管的项目,并将其作为指导。一些项目在其存储库中包含定制脚本,以便于访问(许多Kitware的项目都是这样做的,并且这些脚本已经有相当好的文档记录)。

24.8.4. 测试标准和结果

上面的例子简单地展示了文件上传可以合并到一个自定义的CTest脚本中。ctest_upload()命令提供了用于记录要上传文件的机制,并将其附加到CDash的构建结果中,在随后的ctest_submit()调用中执行上传操作。但是,有时文件上传应该与特定的测试相关联,而不是与整个脚本运行相关联。为此,CMake提供了ATTACHED_FILES和ATTACHED_FILES_ON_FAIL测试属性。两者都包含一个要上传的文件列表,并与特定的测试相关联,唯一的区别是后者包含的文件只有在测试失败时才上传。这是一种非常有用的方法,可以记录有关故障的额外信息,以便进行进一步的调试。

add_executable(doGen ...)
add_test(NAME generateFile COMMAND doGen)
set_tests_properties(generateFile PROPERTIES
 ATTACHED_FILES_ON_FAIL
 ${CMAKE_CURRENT_BINARY_DIR}/generated.c
 ${CMAKE_CURRENT_BINARY_DIR}/generated.h
)

测试还可以记录单个测量值,在CDash中的每次测试提交都会进行记录和跟踪。形式通常是key=value,虽然=value部分可以省略,使用默认值1。测量会记录成测试特性,如下所示:

set_tests_properties(perfRun PROPERTIES
 MEASUREMENT mySpeed=${someValue}
)

因为测量值必须在测试运行之前定义,所以这样做的用处有限。更有用的是Vtk等项目大量使用的未文档化特性,以及围绕它构建的项目,这些项目中,测量值以类似于HTML标记的形式嵌入到测试输出本身中。ctest扫描这些度量值的输出,提取相关数据并将其作为测试结果的一部分上传到CDash。这些测量结果随后显示在test details页面顶部的结果表中。最简单的测量类型定义如下:

<DartMeasurement name="key" type="someType">value</DartMeasurement>

name作为结果表中测量值的标签,type类似于文本/字符串或数值/双引号的属性。值是对测量有意义的文本或数字内容。对于数值,CDash提供了工具来绘制最近测试运行中每次测量的历史,这对于发现随时间变化的行为非常有用。

另一种形式可以用来嵌入一个文件,而不是一个特定的值:

<DartMeasurementFile name="key" type="someType">filePath</DartMeasurementFile>

第二种形式对于上传图片最有用,类型属性应该是image/png或image/jpeg之类的。filePath值应该是要上传文件的绝对路径。

CDash可以识别一些特殊的图像测量名称。这可用于帮助比较预期的和实际的图像,CDash甚至为重叠比较提供了交互式UI。识别的名称属性及其含义包括:

TestImage

为测试生成的图像。它可以认为是测试输出,并将单独显示,也将作为交互式比较图像的一部分。

ValidImage

这相当于测试的期望图像。应该与测量值具有相同的尺寸,但不一定要求具有相同的图像格式。只包括交互式图像。

DifferenceImage2

可以使用各种工具来生成两个图像之间的差异。当测试提供这样的图像文件时,可以使用这个名称将其包含在上载到CDash的测试输出中。比较图像将纳入交互式比较图像中。

24.9. GoogleTest

CMake/ctest为构建、执行和确定测试通过/失败状态提供支持。该项目负责提供测试代码本身,就可以使用类似GoogleTest这样的测试框架。这样的框架补充了CMake和ctest所提供的特性,便于编写清晰、结构良好的测试用例,从而很好地集成到CMake和ctest的工作方式中。

CMake通过FindGTest模块支持GoogleTest。该模块搜索预先构建的GoogleTest的位置,并创建项目可以使用的变量将GoogleTest合并到他们的构建中。CMake 3.5中,还提供了导入目标,这比使用变量更可取。使用这些导入目标可以更健壮地处理使用需求和属性。下面是使用CMake 3.5或更高版本的示例:

add_executable(myGTestCases ...)

find_package(GTest REQUIRED)
target_link_libraries(myGTestCases PRIVATE GTest::GTest)

add_test(NAME myGTestCases COMMAND myGTestCases)

导入目标负责确保在构建myGTestCases时,使用相关的头文件搜索路径,以及在需要时链接线程库等内容。上面的代码可以在所有平台上运行,隐藏了与不同平台和编译器上使用的不同名称、运行时、标志等相关的复杂性。如果使用模块定义的变量,不是导入目标,这些事情大多必须手动处理,那么这就是一项相当危险的任务。

更健壮的方法是将GoogleTest的源代码直接合并到构建中,而不是依赖于已有的预构建二进制文件。这确保GoogleTest与项目的其余部分使用完全相同的编译器和链接器构建,从而避免了在使用预构建的GoogleTest二进制文件时可能出现的许多问题。项目可以通过多种方式做到这一点,每种方式都有其优缺点。在项目中创建源文件和头文件的副本是最简单的,但它会使项目与将来可能对GoogleTest进行的改进失去联系。GoogleTest git存储库可以作为git子模块添加到项目中,但这也有其自身的健壮性问题。作为配置步骤的一部分,下载GoogleTest源代码的第三个选项在27.2节中详细讨论,“FetchContent”,它也有一些缺点(CMake 3.11中添加的特性也使它用起来变得非常容易)。

使用GoogleTest的测试可执行文件通常定义多个测试用例。通常是只运行一次可执行文件,并假设它是一个单独的测试用例,这种模式并不合适。理想情况下,每个GoogleTest测试用例都应该对ctest可见,这样每个测试用例都可以单独运行和评估。FindGTest模块提供了一个gtest_add_test()函数,该函数扫描源代码,查找相关GoogleTest宏的使用情况,并提取出每个单独的测试用例作为自己的ctest测试。这个命令的示例:

gtest_add_tests(executable "extraArgs" sourceFiles..)

CMake 3.1中,扫描的sourceFiles列表可以用关键字AUTO代替,在这种情况下,通过假设可执行文件是CMake目标,并使用它的SOURCES目标属性来获得源列表。

CMake 3.9中,项目可以使用gtest_add_tests()函数和由项目本身构建的GoogleTest。这意味着该项目不需要Find模块,因此该功能移到新的GoogleTest模块,使用FindGTest将其包含进来,以保持向后兼容性。作为完成这项工作的一部分,还改进了使用形式:

gtest_add_tests(
 TARGET target
 [SOURCES src1...]
 [EXTRA_ARGS arg1...]
 [WORKING_DIRECTORY dir]
 [TEST_PREFIX prefix]
 [TEST_SUFFIX suffix]
 [SKIP_DEPENDENCY]
 [TEST_LIST outVar]
)

仍然支持之前的形式,但是项目应该在可能的情况下更倾向于使用新形式,因为它更灵活、更健壮。例如,可以给带有不同参数的对gtest_add_tests()的多次调用提供相同的目标,每个调用具有不同的TEST_PREFIX和/或TEST_SUFFIX,以区分生成的测试集。新形式还提供了在给出TEST_LIST选项时添加一组测试。有了可用的测试名称,项目就能够根据需要修改测试的属性。下面的示例演示了这些功能:

# Assume GoogleTest is already part of the build, so we don't need
# FindGTest and can reference the gtest target directly
include(GoogleTest)
add_executable(testDriver ...)
target_link_libraries(testDriver PRIVATE gtest)

# Run the testDriver twice with two different arguments
gtest_add_tests(
 TARGET testDriver
 EXTRA_ARGS --algo=fast
 TEST_SUFFIX .Fast
 TEST_LIST fastTests
)
gtest_add_tests(
 TARGET testDriver
 EXTRA_ARGS --algo=accurate
 TEST_SUFFIX .Accurate
 TEST_LIST accurateTests
)
set_tests_properties(${fastTests} PROPERTIES TIMEOUT 3)
set_tests_properties(${accurateTests} PROPERTIES TIMEOUT 20)

set(betaTests ${fastTests} ${accurateTests})
list(FILTER betaTests INCLUDE REGEX Beta)
set_tests_properties(${betaTests} PROPERTIES LABELS Beta)

上面的示例创建了两组测试,然后对它们设置了不同的超时限制。每组测试的名称都有不同的后缀。如果没有TEST_SUFFIX选项,对gtest_add_tests()的第二次调用将失败,因为这将尝试创建与第一次调用同名的测试。该示例还为某些测试设置了Beta标签,而不管它们属于哪个测试集。

虽然gtest_add_tests()对于没有特殊格式的简单用例和源文件处理得很好,但它不能处理参数化测试或通过自定义宏定义的测试。还需要重新运行CMake,以便在测试源更改时重新扫描源文件。如果CMake的步骤不够快,编写测试代码时可能会令人沮丧,因为在每次更改之后,CMake将为新的测试代码重新运行。SKIP_DEPENDENCY选项可以防止这种行为,并依赖于开发人员手动重新运行CMake来更新测试集,这更多针对的是测试时的临时解决方案,而不应永久保留。

CMake 3.10中,添加了新函数来解决gtest_add_tests()的缺点,它要求可执行文件在运行ctest时列出它的测试,而不是在CMake时扫描源代码时。因此,每当测试源发生更改时,不需要重新运行CMake,参数化测试将得到处理,并且对测试的格式或定义没有任何限制。唯一的权衡是,测试列表在CMake运行期间不可用,只有在实际运行ctest时才能获得。

gtest_discover_tests(target
 [EXTRA_ARGS arg1...]
 [WORKING_DIRECTORY dir]
 [TEST_PREFIX prefix]
 [TEST_SUFFIX suffix]
 [NO_PRETTY_TYPES]
 [NO_PRETTY_VALUES]
 [PROPERTIES name1 value1...]
 [TEST_LIST var]
 [DISCOVERY_TIMEOUT seconds] # See notes below
)

默认情况下,生成参数化测试的名称时,函数将尝试使用类型或值名称,而不是数字索引。这通常会产生可读性更强、更有用的名称,但对于不希望这样做的情况,可以使用NO_PRETTY_TYPES和NO_PRETTY_VALUES选项来禁止替换,只使用索引值。

DISCOVERY_TIMEOUT选项指的是运行可执行文件,以获得测试列表所花费的时间。默认的5秒对于所有的可执行程序来说已经足够了,但是对于那些有大量测试的可执行程序,或者其他一些导致返回测试列表花费很长时间的行为来说就不够了。这个特殊的选项最初是在CMake 3.10.1中添加的,其关键字名称为TIMEOUT,但发现会与TIMEOUT测试属性冲突,从而导致意外但合法的行为。在CMake 3.10.3中,关键字被更改为DISCOVERY_TIMEOUT,以防止出现这种情况。

由于测试列表没有返回给调用者,因此不可调用set_tests_properties()或set_property()来修改测试的属性。相反,gtest_discover_tests()允许将属性值指定为调用的一部分,然后将其写入ctest输入文件,以便在运行ctest时应用。虽然不能提供在CMake中遍历已发现的测试集,并单独处理它们,但将已发现测试的属性作为一个整体设置的能力,通常是所需要的。这方面的例外是,不能设置具有与gtest_discover_tests()命令中的关键字相同名称的测试属性,也不能设置属性列表的值。可以使用自定义的ctest脚本来处理这种情况,下面给出了一个示例。

TEST_LIST选项对于gtest_discover_tests()和gtest_add_tests()的工作方式不同。本例中,此选项给出的变量名用于CMake输出(作为ctest的输入)文件中,而不是直接用于CMake。TEST_LIST选项只在项目向生成的ctest输入文件中添加一些自己的自定义逻辑,并引用生成的测试列表时才需要。即使这样,也只有在对gtest_discover_tests()的多次调用中使用相同的目标时,才有必要这样做。如果没有对TEST_LIST选项设置,则使用_TESTS的默认变量名。

可以通过将文件名附加到TEST_INCLUDE_FILES目录属性中保存的文件列表中来添加定制代码。项目不能覆盖这个目录属性,只能对它进行追加,因为gtest_discover_tests()使用该属性来构建要由ctest读取的文件集。下面的示例展示了如何使用自定义文件操作测试的属性,并实现与前面的gtest_add_tests()示例相同的逻辑,包括解决超时名称冲突的解决方案:

gtest_discover_tests(
 testDriver
 EXTRA_ARGS --algo=fast
 TEST_SUFFIX .Fast
 TEST_LIST fastTests
)
gtest_discover_tests(
 testDriver
 EXTRA_ARGS --algo=accurate
 TEST_SUFFIX .Accurate
 TEST_LIST accurateTests
)
set_property(DIRECTORY APPEND PROPERTY
 TEST_INCLUDE_FILES ${CMAKE_CURRENT_LIST_DIR}/customTestManip.cmake
)

customTestManip.cmake

# Set here to work around the TIMEOUT keyword clash with the
# gtest_discover_tests() call, works with all CMake versions
set_tests_properties(${fastTests} PROPERTIES TIMEOUT 3)
set_tests_properties(${accurateTests} PROPERTIES TIMEOUT 20)

set(betaTests ${fastTests} ${accurateTests})
list(FILTER betaTests INCLUDE REGEX Beta)
set_tests_properties(${betaTests} PROPERTIES LABELS Beta)

使用自定义的ctest脚本给项目增加了一点复杂性,允许完全控制测试属性。不需要担心与gtest_discover_tests()的名称冲突,并且可以安全地处理列表值的属性。

24.10. 总结

每个测试的名称应该简短,又足够描述于测试的性质,以便使用正则表达式和ctest的-R和-E选项缩小测试集的范围。避免在名称中包含test,因为它只用于向测试输出添加额外内容,除此之外没有任何用处。

假设某一天项目可能合并到更大的层次结构中,其中可能有许多其他测试。在所有项目中保持测试名的唯一可能是困难的,但是与其在每个测试名中包含一个特定于项目的字符串,不如考虑使用LABELS测试属性来为每个测试包含一个特定于项目的标签。这些标签允许通过正则表达式-L和-LE选项包含或排除测试。测试可以有多个标签,因此这对如何使用其他标签没有限制,但可能使测试使用起来更方便。

标签的另一个好用法是识别预期要运行很长时间的测试。开发人员和持续集成系统可能不希望频繁地运行它们,因此能够基于测试标签排除它们非常方便。考虑向运行时间较长且不需要经常运行的测试添加标签。在没有任何其他现有惯例的情况下,使用“长期”(LongRunning)这个标签是一个不错的选择。

除了使用正则表达式匹配测试名称和标签外,还可以将测试集缩小到特定目录以下。不用从构建树的顶层运行ctest,可以从它下面的子目录运行。ctest只知道从该目录的关联源目录及其下定义的那些测试。为了能够充分利用这一点,不应该将所有测试都收集在一个地方,并且不使用目录结构进行定义。让测试靠近它们正在测试的源代码可能会很有用,这样可以重用源代码的自然目录结构,也为测试提供结构。如果要移动源代码,这种方法还可以更容易地移动与其关联的测试。

编写测试很容易,打开日志记录,然后使用pass/fail的正则表达式来确定是否成功。这可能是一种相当粗糙的方法,因为开发人员经常更改日志输出,这里假设它只是为了提供信息。向日志输出中添加时间戳将使这种方法更加复杂。如果可能的话,最好让测试代码本身通过显式地测试预期的前置和后置条件、中间值等方式来确定成功或失败状态,而不是依赖于匹配日志记录的输出。像GoogleTest这样的测试框架使得编写和维护这样的测试变得相当容易,并且强烈推荐使用这些框架(使用哪个框架不重要,重要的是使用适合自己的)。

如果使用GoogleTest框架,可以考虑使用GoogleTest模块提供的gtest_add_tests()和gtest_discover_tests()函数。如果测试代码足够简单,gtest_add_tests()可以找到所有测试,它提供了操作单个测试属性的方法,但是在处理测试代码本身时可能不太方便,因为可能需要频繁地重新运行CMake。如果项目需要CMake 3.10.3或更高版本作为最低版本,那么gtest_discover_tests()可能更合适。如果遵循上面关于使用测试标签的建议,此函数的主要缺点是将测试属性设置为列表,所以需要做很多额外的工作,这一点尤其重要。如果需要支持3.9之前的CMake版本,则只能使用gtest_add_tests(),并且只能使用更简单形式。该项目还将需要使用FindGTest模块,而不是GoogleTest模块,如果GoogleTest是作为项目本身的一部分构建的,将进一步增加复杂性。因此强烈建议使用GoogleTest的项目迁移到CMake 3.9或更高版本,最好是3.10.3或更高版本。

对于不同目标平台进行交叉编译的项目,考虑是否可以将测试编写为在模拟器下运行,或者通过脚本或等效机制在远程系统上执行。CMake的CMAKE_CROSSCOMPILING_EMULATOR变量和相关的CROSSCOMPILING_EMULATOR目标属性可以用于实现这两种策略。理想情况下,CMAKE_CROSSCOMPILING_EMULATOR应该在交叉编译工具链文件中设置

充分利用ctest中对并行测试执行的支持。如果已知测试使用多个CPU,请设置这些测试的处理器属性,以便为ctest提供调度指导。如果测试需要独占访问共享资源,请使用RESOURCE_LOCK属性来控制对该资源的访问,避免使用RUN_SERIAL测试属性,除非没有其他选择。RUN_SERIAL可能会对并行测试性能产生很大的负面影响,而且除了快速的、临时的开发人员实验之外,使用RUN_SERIAL就是不合理的。如果运行ctest的机器上可能有其他进程造成CPU负载,可以考虑使用-l选项来限制CPU上的过度提交。这在开发人员机器上特别有用,因为开发人员可能同时为多个项目构建和运行测试。

如果最低CMake版本可以设置为3.7或更高,最好使用测试固件来定义测试之间的依赖关系。定义测试用例,以设置和清理其他测试所需的资源,启动和停止服务等等。减少运行时测试集的正则表达式匹配或使用选项--rerun-failed,ctest会自动添加所需的固件测试测试集。固件也确保跳过测试依赖关系的失败,与DEPENDS测试属性不同,固件只控制测试顺序。要获得对将自动添加到测试集中,且满足固件依赖关系的测试的细粒度控制,可以使用CMake 3.9或更高版本,使用ctest选项-FS、-FC和-FA。项目仍然只需要CMake 3.7作为最低版本。另外,由于依赖关系和时间控制很清楚,所以可以将TIMEOUT_AFTER_MATCH测试属性和固件一起使用。

ctest构建和测试模式可以将小型测试构建合并到主项目的测试套件中作为测试用例。当某些测试构建需要验证某些情况会导致配置或构建错误时,这些方法尤其有效。因为测试用例可以定义为预期的失败,所以可以验证这样的条件,而不会导致主项目的构建失败。考虑使用ctest构建和测试模式作为调用add_test()的COMMAND参数,从而定义这样的测试用例。

为了主项目的完整配置、构建和测试,请考虑CDash集成特性提供的功能,而不是使用ctest构建和测试模式。CDash集成特性可以更好地捕获整个过程的输出,并提供自定义每个步骤行为的机制。还有一些额外的特性,可以方便地使用代码覆盖和动态分析工具,比如内存检查器等,而且无论是否将结果提交给CDash服务器,这些特性都可以使用。实际上,驱动整个CDash流程的定制ctest脚本功能可以在不使用CDash的情况下使用,这使得它成为一种独立于平台的方式,可以为其他持续集成系统编写整个构建和测试流程的脚本。CDash服务器还可以与其他CI系统一起使用,以提供更丰富的特性,用于记录和比较构建历史、测试失败趋势等等。

Last updated