单元测试#
单元测试是测试的第一阶段,用于验证源代码单元,例如类和函数. 通常,通过验证代码单元针对各种输入的输出来测试代码单元. 单元测试有助于确保代码按预期运行,并防止行为发生意外更改.
Autoware 使用 ament_cmake 框架来构建和运行测试.
相同的框架也用于分析测试结果.
ament_cmake 提供了几个方便的函数,可以轻松地在基于 CMake 的包中注册测试,并确保生成与 JUnit 兼容的结果文件.
它目前支持几种不同的测试框架,如 pytest、gtest 和 gmock.
为了防止并行运行的测试在发布和订阅 ROS 主题时相互干扰,
建议使用 ament_cmake_ros 中的命令来独立运行测试.
有关将 ament_add_ros_isolated_gtest 与 colcon test 一起使用的示例,请参见下文.
所有其他测试都遵循类似的模式.
使用 gtest 创建单元测试#
在 my_cool_pkg/test 中,创建 gtest 代码文件 test_my_cool_pkg.cpp:
#include "gtest/gtest.h"
#include "my_cool_pkg/my_cool_pkg.hpp"
TEST(TestMyCoolPkg, TestHello) {
EXPECT_EQ(my_cool_pkg::print_hello(), 0);
}
在 package.xml 中,添加以下行:
<test_depend>ament_cmake_ros</test_depend>
接下来,在 CMakeLists.txt 的 BUILD_TESTING 下添加一个条目来编译测试源文件:
if(BUILD_TESTING)
ament_add_ros_isolated_gtest(test_my_cool_pkg test/test_my_cool_pkg.cpp)
target_link_libraries(test_my_cool_pkg ${PROJECT_NAME})
target_include_directories(test_my_cool_pkg PRIVATE src) # For private headers.
...
endif()
这会自动将测试与 gtest 提供的默认 main 函数链接起来.
被测代码通常位于不同的 CMake 目标(示例中为 ${PROJECT_NAME})中,并且需要添加其用于链接的共享对象.
如果测试源文件包含来自 src 目录的私有头文件,则需要使用 target_include_directories()` 函数将该目录添加到 include 路径中.
要注册新的 gtest 项,请使用宏 TEST () 包装测试代码.
TEST () 是一个预定义的宏,有助于生成最终的测试代码,
并且还注册了一个 gtest 项以供执行.
测试用例名称应为 CamelCase,因为在创建测试可执行文件时,gtest 会在 fixture 名称和类 case 名称之间插入下划线.
gtest/gtest.h 还包含 gtest 的预定义宏,如 ASSERT_TRUE(condition),
ASSERT_FALSE(condition), ASSERT_EQ(val1,val2), ASSERT_STREQ(str1,str2), EXPECT_EQ()等.
如果不满足条件,ASSERT_* 将中止测试,
而 EXPECT_* 会将测试标记为失败,但会继续执行下一个测试条件.
提示
有关 gtest 及其功能的更多信息可以在 gtest repo 中找到.
在演示 CMakeLists.txt 中,ament_add_ros_isolated_gtest 是 ament_cmake_ros 中的预定义宏,有助于简化添加 gtest 代码的过程.
详细信息可以在 ament_add_gtest.cmake 中查看.
构建测试#
默认情况下,所有必要的测试文件 (ELF, CTestTestfile.cmake 等 都由 colcon 编译:
cd ~/workspace/
colcon build --packages-select my_cool_pkg
测试文件在 ~/workspace/build/my_cool_pkg 下生成.
运行 test#
要为特定软件包运行所有测试,请调用:
$ colcon test --packages-select my_cool_pkg
Starting >>> my_cool_pkg
Finished <<< my_cool_pkg [7.80s]
Summary: 1 package finished [9.27s]
test 命令输出包含所有测试结果的简要报告.
要获取所有已执行测试的作业信息,请调用:
$ colcon test-result --all
build/my_cool_pkg/test_results/my_cool_pkg/copyright.xunit.xml: 8 tests, 0 errors, 0 failures, 0 skipped
build/my_cool_pkg/test_results/my_cool_pkg/cppcheck.xunit.xml: 6 tests, 0 errors, 0 failures, 0 skipped
build/my_cool_pkg/test_results/my_cool_pkg/lint_cmake.xunit.xml: 1 test, 0 errors, 0 failures, 0 skipped
build/my_cool_pkg/test_results/my_cool_pkg/my_cool_pkg_exe_integration_test.xunit.xml: 1 test, 0 errors, 0 failures, 0 skipped
build/my_cool_pkg/test_results/my_cool_pkg/test_my_cool_pkg.gtest.xml: 1 test, 0 errors, 0 failures, 0 skipped
build/my_cool_pkg/test_results/my_cool_pkg/xmllint.xunit.xml: 1 test, 0 errors, 0 failures, 0 skipped
Summary: 18 tests, 0 errors, 0 failures, 0 skipped
在 ~/workspace/log/test_<date>/<package_name> 目录中查找所有原始测试命令、std_out 和 std_err .
还有 ~/workspace/log/latest_*/ 目录,其中包含指向最新包级构建和测试输出的符号链接.
要在运行测试时打印测试的详细信息,请使用 --event-handlers console_cohesion+ 选项将详细信息直接打印到控制台:
$ colcon test --event-handlers console_cohesion+ --packages-select my_cool_pkg
...
test 1
Start 1: test_my_cool_pkg
1: Test command: /usr/bin/python3 "-u" "~/workspace/install/share/ament_cmake_test/cmake/run_test.py" "~/workspace/build/my_cool_pkg/test_results/my_cool_pkg/test_my_cool_pkg.gtest.xml" "--package-name" "my_cool_pkg" "--output-file" "~/workspace/build/my_cool_pkg/ament_cmake_gtest/test_my_cool_pkg.txt" "--command" "~/workspace/build/my_cool_pkg/test_my_cool_pkg" "--gtest_output=xml:~/workspace/build/my_cool_pkg/test_results/my_cool_pkg/test_my_cool_pkg.gtest.xml"
1: Test timeout computed to be: 60
1: -- run_test.py: invoking following command in `~/workspace/src/my_cool_pkg`:
1: - ~/workspace/build/my_cool_pkg/test_my_cool_pkg --gtest_output=xml:~/workspace/build/my_cool_pkg/test_results/my_cool_pkg/test_my_cool_pkg.gtest.xml
1: [==========] Running 1 test from 1 test case.
1: [----------] Global test environment set-up.
1: [----------] 1 test from test_my_cool_pkg
1: [ RUN ] test_my_cool_pkg.test_hello
1: Hello World
1: [ OK ] test_my_cool_pkg.test_hello (0 ms)
1: [----------] 1 test from test_my_cool_pkg (0 ms total)
1:
1: [----------] Global test environment tear-down
1: [==========] 1 test from 1 test case ran. (0 ms total)
1: [ PASSED ] 1 test.
1: -- run_test.py: return code 0
1: -- run_test.py: inject classname prefix into gtest result file `~/workspace/build/my_cool_pkg/test_results/my_cool_pkg/test_my_cool_pkg.gtest.xml`
1: -- run_test.py: verify result file `~/workspace/build/my_cool_pkg/test_results/my_cool_pkg/test_my_cool_pkg.gtest.xml`
1/5 Test #1: test_my_cool_pkg ................... Passed 0.09 sec
...
100% tests passed, 0 tests failed out of 5
Label Time Summary:
copyright = 0.49 sec*proc (1 test)
cppcheck = 0.20 sec*proc (1 test)
gtest = 0.05 sec*proc (1 test)
lint_cmake = 0.18 sec*proc (1 test)
linter = 1.34 sec*proc (4 tests)
xmllint = 0.47 sec*proc (1 test)
Total Test time (real) = 7.91 sec
...
代码覆盖率#
粗略地描述, 代码覆盖率指标是衡量在测试期间执行(覆盖)了多少程序代码的指标.
在 Autoware 存储库中,Codecov 用于自动计算任何打开的拉取请求的覆盖率.
有关代码覆盖率指标的更多详细信息,请参阅 Codecov 文档.