Skip to content

单元测试#

单元测试是测试的第一阶段,用于验证源代码单元,例如类和函数. 通常,通过验证代码单元针对各种输入的输出来测试代码单元. 单元测试有助于确保代码按预期运行,并防止行为发生意外更改.

Autoware 使用 ament_cmake 框架来构建和运行测试. 相同的框架也用于分析测试结果.

ament_cmake 提供了几个方便的函数,可以轻松地在基于 CMake 的包中注册测试,并确保生成与 JUnit 兼容的结果文件. 它目前支持几种不同的测试框架,如 pytestgtestgmock.

为了防止并行运行的测试在发布和订阅 ROS 主题时相互干扰, 建议使用 ament_cmake_ros 中的命令来独立运行测试.

有关将 ament_add_ros_isolated_gtestcolcon 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.txtBUILD_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_gtestament_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_outstd_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 文档.