ROS2 库包设置和使用 Catch2 进行单元测试

说明

本文的目的是了解如何在 ROS2 中创建库,以供其他 ROS2 包使用。除此之外,本文还介绍了如何使用 catch2 框架编写单元测试。本文的第 1 部分将详细介绍如何创建库包。第 2 部分将介绍 ROS2 软件包如何利用创建的库

上篇 ROS2 库包设置和使用 Catch2 进行单元测试

一、项目结构大纲

库的流程

  1. 在包含/点下为 2d 点创建标题.hpp
  2. 在 src/point 下为 2d 点添加实现类.cpp
  3. 为测试下的测试类/主类创建入口点.cpp
  4. 在测试/test_point.cpp下添加单元测试
  5. 在 cmake/try_out_utils-config.cmake.in 下为 cmake 添加命名空间配置
  6. 在根项目目录下创建 CMakeLists.txt (此处指定了 lib 的配置)
  7. 在根项目目录下创建包.xml

二、程序代码实现

2.1 为 2d 点创建标题.hpp

复制代码
// include/try_out_utils/point.hpp

#ifndef TRY_OUT_UTILS__POINT_HPP_
#define TRY_OUT_UTILS__POINT_HPP_

namespace try_out_utils
{
class Point
{
private:
  double x_;
  double y_;

public:
  Point(double x, double y);
  double get_x();
  double get_y();
};
}  // namespace try_out_utils

#endif  // TRY_OUT_UTILS__POINT_HPP_

2.2 为 2d 点添加实现类.cpp

具有两个私有变量 x 和 y 的标头类,用于表示 2d 中的点及其 getter 和 setter 方法

复制代码
// src/point.cpp
#include <try_out_utils/point.hpp>
Point::Point(double x, double y)
{
  this->x_ = x;
  this->y_ = y;
}
double Point::get_x()
{
  return this->x_;
}
double Point::get_y()
{
  return this->y_;
}

之前在标头类中声明的 2d 点的实现类

复制代码
// test/main.cpp
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>

测试用例的 Catch2 入口点

复制代码
// test/test_point.cpp
#include <catch2/catch.hpp>
#include <try_out_utils/point.hpp>
TEST_CASE("Test for point", "[]")
{
  SECTION("Test for point with object creation")
  {
    Point p(11, 10);
    REQUIRE(p.get_x() == 11);
    REQUIRE(p.get_y() == 10);
  }
}

对点类进行单元测试以创建新的点对象

复制代码
@PACKAGE_INIT@

get_filename_component(try_out_utils_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)

include(CMakeFindDependencyMacro)

if(NOT TARGET try_out_utils::try_out_utils)
    include("${try_out_utils_CMAKE_DIR}/try_out_utils-targets.cmake")
endif()

check_required_components(try_out_utils)

三、编译文件CMake

Cmake 配置别名,以便外部包可以引用该库作为 try_out_utils::try_out_utils

复制代码
# setting up cmake minimum version and project name
cmake_minimum_required(VERSION 3.8)
project(try_out_utils VERSION 0.1.0)

# setting c++ version standard to 17
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 17)
endif()

# adding compiler arguments
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# added to use install directory variables
include(GNUInstallDirs)

# adding external dependencies required
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(ament_cmake_catch2 REQUIRED)
find_package(Catch2 REQUIRED)

# creating library package with reference to required files
add_library(${PROJECT_NAME} SHARED
  src/point.cpp
)

# including external directories reference for the created library
target_include_directories(try_out_utils PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)

# helpers functions for creating config files that can be included by other projects to find and use a package
include(CMakePackageConfigHelpers)

set(INSTALL_CONFIG_DIR "${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}/cmake")
set(PACKAGE_CONFIG_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake")
set(PACKAGE_CONFIG_FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake")

# creating version configuration for external package to perform compatibility check
configure_package_config_file(
  "${CMAKE_CURRENT_LIST_DIR}/cmake/${PROJECT_NAME}-config.cmake.in"
  ${PACKAGE_CONFIG_FILE}
  INSTALL_DESTINATION ${INSTALL_CONFIG_DIR}
)

# creating version configuration for external package to perform compatibility check
write_basic_package_version_file(
  ${PACKAGE_CONFIG_VERSION_FILE}
  COMPATIBILITY ExactVersion
)

# installing library files
install(
  TARGETS try_out_utils
  EXPORT try_out_utils-targets
  DESTINATION lib
)

# installing all reference header files
install(
    DIRECTORY include/try_out_utils
    DESTINATION include/
)

# installing cmake config files for try_out_utils
install(
  FILES
    ${PACKAGE_CONFIG_VERSION_FILE}
    ${PACKAGE_CONFIG_FILE}
  DESTINATION ${INSTALL_CONFIG_DIR}
)

# installing cmake config files for try_out_utils-targets
install(
  EXPORT try_out_utils-targets
  FILE try_out_utils-targets.cmake
  NAMESPACE try_out_utils::
  DESTINATION ${INSTALL_CONFIG_DIR}
)

# exporting the try_out_utils-target cmake config to build folder
export(
  EXPORT try_out_utils-targets
  FILE ${CMAKE_CURRENT_BINARY_DIR}/try_out_utils-targets.cmake
  NAMESPACE try_out_utils::
)

# checking whether build includes test
if(BUILD_TESTING)
  # listing files for testing
  file(GLOB_RECURSE unit_test_srcs "test/*.cpp")

  # adding listed files for testing
  ament_add_catch2(test_try_out_utils ${unit_test_srcs} TIMEOUT 300)

  # linking libraries required to the current package for testing
  target_link_libraries(
    test_try_out_utils
    try_out_utils
    Catch2::Catch2
  )

  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

让我们详细介绍一下上面创建的cmakelist

  1. include(GNUInstallDirs) --- 允许使用 cmake 安装变量

  2. add_library--- 使用指定的参照文件创建库

  3. target_include_directories--- 要包含在目标中的内部和外部包中的目录

  4. include(CMakePackageConfigHelpers) --- 用于创建配置文件的帮助程序函数,其他项目可以包含这些文件来查找和使用包

  5. configure_package_config_file --- 在创建用于安装项目或库的 or 文件时,应使用代替普通命令。它通过避免已安装文件中的硬编码路径来帮助使生成的包可重定位configure_package_config_file()configure_file()<PackageName>Config.cmake``<PackageName>-config.cmake``Config.cmake

  6. write_basic_package_version_file--- 应该用于创建版本配置文件,以便导入此库的外部包可以执行与此处提供的版本兼容性检查

  7. 在目标位置安装目录 --- 表单将一个或多个目录的内容安装到给定目标DIRECTORY

  8. 在目标位置安装 文件 --- 表单指定为项目安装文件的规则。在指定目标中安装文件FILES

  9. 在目标位置安装导出 --- 该窗体生成并安装一个 Make 文件,其中包含用于将目标从安装树导入到另一个项目中的代码EXPORT

  10. 出口 --- 创建一个可能由外部项目包含的文件,以从当前项目的生成树中导入命名的目标。这在交叉编译期间非常有用,可以生成实用程序可执行文件,这些实用程序可执行文件可以在一个项目中的主机平台上运行,然后将它们导入到为目标平台编译的另一个项目中。将字符串附加到写入文件的所有目标名称前面<filename>``<target>...``(NAMESPACE)``<namespace>

  11. 使用导出在目标位置安装目标 --- 在指定目标位置安装目标的代码段。选项将已安装的目标文件与名为EXPORT``<export-name>

  12. ament_add_catch2--- 使用为测试提供的测试文件创建测试目标

  13. target_link_libraries --- 将目标与指定的
    库链接起来(Ament 是一个用 Cmake 编写的包装器,用于简化 colcon 构建的一些功能)

    <?xml version="1.0"?> <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> <package format="3"> <name>try_out_utils</name> <version>0.1.0</version> <description>Utility package for commonly used functions</description> <maintainer email="sample@email.com">santosh balaji</maintainer> <license>Apache License 2.0</license>

    <buildtool_depend>ament_cmake</buildtool_depend>

    <test_depend>ament_lint_auto</test_depend>
    <test_depend>ament_lint_common</test_depend>
    <test_depend>ament_cmake_catch2</test_depend>
    <test_depend>ament_cmake_uncrustify</test_depend>

    <export> <build_type>ament_cmake</build_type> </export> </package>

四、包依赖关系

用于指定依赖项的包文件

复制代码
# To build created package
colcon build --packages-select try_out_utils

# To run tests on package
colcon test --event-handlers console_direct+ --packages-select try_out_utils

运行上述命令以构建和测试库包

执行后的测试结果

下篇 ROS2 库包设置和使用 Catch2 进行单元测试

五、项目结构大纲

六、库的流程

  1. 在包含/try_out/point_checker.hpp 下为逻辑函数创建标头
  2. 在 src/point_checker.cpp 下为逻辑函数添加实现类
  3. 为测试下的测试类/主类创建入口点.cpp
  4. 在测试/test_point_checker.cpp下添加单元测试
  5. 在根项目目录下创建 CMakeLists.txt
  6. 在根项目目录下创建包.xml

七、程序实现

复制代码
// include/try_out/point_checker.hpp
#ifndef TRY_OUT__POINT_CHECKER_HPP_
#define TRY_OUT__POINT_CHECKER_HPP_

#include <try_out_utils/point.hpp>
#include <memory>
#include <cmath>
#include <vector>

class PointChecker
{
private:
  std::vector<try_out_utils::Point *> points_;

public:
  void add_point(double x, double y);
  std::vector<std::vector<double>> find_distance_matrix();
};

#endif  // TRY_OUT__POINT_CHECKER_HPP_

带有向量的标头类,用于存储点和逻辑函数

复制代码
// src/point_checker.cpp

#include <try_out/point_checker.hpp>

#include <vector>

void PointChecker::add_point(double x, double y)
{
  try_out_utils::Point * point = new try_out_utils::Point(x, y);
  this->points_.push_back(point);
}

std::vector<std::vector<double>> PointChecker::find_distance_matrix()
{
  std::vector<std::vector<double>> overall_vect;
  for (unsigned int i = 0; i < this->points_.size(); i++) {
    std::vector<double> inner_vect;
    for (unsigned int j = 0; j < this->points_.size(); j++) {
      double x_compute =
        (this->points_[j]->get_x() - this->points_[i]->get_x()) *
        (this->points_[j]->get_x() - this->points_[i]->get_x());
      double y_compute =
        (this->points_[j]->get_y() - this->points_[i]->get_y()) *
        (this->points_[j]->get_y() - this->points_[i]->get_y());
      double distance = std::sqrt(x_compute + y_compute);
      inner_vect.push_back(distance);
    }
    overall_vect.push_back(inner_vect);
  }
  return overall_vect;
}

int main()
{
  PointChecker point_checker;
  point_checker.add_point(5, 5);
  return 0;
}

前面在标头中声明的逻辑类的实现类。此处添加了距离计算逻辑

复制代码
// test/main.cpp
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>

测试用例的 Catch2 入口点

复制代码
// test/test_point_checker.cpp

#include <catch2/catch.hpp>
#include <try_out/point_checker.hpp>
#include <vector>
#include <cmath>
TEST_CASE("Test for point checker", "[]")
{
  SECTION("Test for distance matrix computation"
  {
    PointChecker point_checker;
    point_checker.add_point(1, 1);
    point_checker.add_point(2, 2);
    point_checker.add_point(3, 3);
    std::vector<std::vector<double>> result = 
      point_checker.find_distance_matrix();
    REQUIRE(result.size() == 3);
    REQUIRE(result[0].size() == 3);
    REQUIRE(result[1].size() == 3);
    REQUIRE(result[2].size() == 3);
    REQUIRE(result[0][0] == 0);
    REQUIRE(std::round(result[0][1] - 1.4142135624) == 0);
    REQUIRE(std::round(result[0][2] - 2.8284271247) == 0);
    REQUIRE(std::round(result[1][2] - 1.4142135624) == 0);
    REQUIRE(result[1][1] == 0);
    REQUIRE(std::round(result[1][2] - 1.4142135624) == 0);
    REQUIRE(std::round(result[2][0] - 2.8284271247) == 0);
    REQUIRE(std::round(result[2][1] - 1.4142135624) == 0); 
    REQUIRE(result[2][2] == 0);
  }
}

点检查器类的单元测试,用于计算提供的点之间的距离

复制代码
# setting up cmake minimum version and project name
cmake_minimum_required(VERSION 3.8)
project(try_out VERSION 0.1.0)

# setting c++ version standard to 17
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 17)
endif()

# adding compiler arguments
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# adding external dependencies required
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(try_out_utils REQUIRED)
find_package(ament_cmake_catch2 REQUIRED)
find_package(Catch2 REQUIRED)

# creating library package with reference to required files
add_library(try_out SHARED
  src/point_checker.cpp
)

# including external directory reference for the created library
target_include_directories(try_out PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
  ${rclcpp_INCLUDE_DIRS})

# including external library reference for the created library
target_link_libraries(try_out
  PUBLIC
    try_out_utils::try_out_utils
)

# creating executables with reference to required files
add_executable(
  try src/point_checker.cpp)

# including external directory reference for the created executable
target_include_directories(try PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
  ${rclcpp_INCLUDE_DIRS})

# including external library reference for the created executable
target_link_libraries(try
  ${rclcpp_LIBRARIES}
  try_out_utils::try_out_utils
)

# installing executable file
install(TARGETS
try
DESTINATION lib})

# installing library file
install(
  TARGETS try_out
  DESTINATION lib
)

# installing all reference header files
install(
    DIRECTORY include/try_out
    DESTINATION include/
)

# checking whether build includes test
if(BUILD_TESTING)
  # listing files for testing
  file(GLOB_RECURSE unit_test_srcs "test/*.cpp")

  # adding listed files for testing
  ament_add_catch2(test_try_out ${unit_test_srcs} TIMEOUT 300)

  # linking libraries required to the current package for testing
  target_link_libraries(test_try_out
  try_out
  try_out_utils::try_out_utils
  Catch2::Catch2)

  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

九、编译环节

让我们详细介绍一下上面创建的cmakelist

  1. add_library--- 使用指定的参照文件创建库

  2. **add_executable ---**使用指定文件创建可执行文件

  3. target_include_directories--- 要包含在目标中的内部和外部包中的目录。可以使用命令

  4. target_link_libraries--- 将目标与指定的库链接

  5. 在目标位置安装目录 --- 表单将一个或多个目录的内容安装到给定目标DIRECTORY

  6. 在目标位置安装目标 --- 表单指定从项目安装目标的规则TARGETS

  7. ament_add_catch2 --- 使用提供的
    测试文件创建测试目标**(Ament 是一个用 Cmake 编写的包装器,用于简化 colcon 构建的一些功能)**

    <?xml version="1.0"?> <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> <package format="3"> <name>try_out</name> <version>0.1.0</version> <description>Work package which utilizes the created library package</description> <maintainer email="santechselva@gmail.com">santosh balaji</maintainer> <license>Apache License 2.0</license>

    <buildtool_depend>ament_cmake</buildtool_depend>

    <depend>try_out_utils</depend>

    <test_depend>ament_lint_auto</test_depend>
    <test_depend>ament_lint_common</test_depend>
    <test_depend>ament_cmake_catch2</test_depend>
    <test_depend>ament_cmake_uncrustify</test_depend>

    <export> <build_type>ament_cmake</build_type> </export> </package>

十、单元测试实现

用于指定依赖项的包文件

复制代码
# To build created package
colcon build --packages-select try_out

# To run tests on package
colcon test --event-handlers console_direct+ --packages-select try_out

运行上述命令以构建和测试库包
执行后的测试结果

参考资料
GitHub - open-rmf/rmf_utils: Internal utilities for RMF libraries (Robotics middleware framework utilities)
GitHub - open-rmf/rmf_traffic: Traffic management libraries for RMF (Traffic management framework which uses the utility library)
CMake Reference Documentation --- CMake 3.27.6 Documentation (Cmake documentation)
ament_cmake user documentation --- ROS 2 Documentation: Foxy documentation (Enhanced version of Cmake for ROS2 packages)

Source code
GitHub - santoshbalaji/ros2-library-package-medium: Repository which has package used for ROS2 based work package tutorial

罗斯2
克马克
第2条军规
科尔康
阿门特

相关推荐
RockHopper202519 小时前
一种面向服务LLM应用系统的显式世界模型架构原理
人工智能·llm·世界模型·显式模型
tap.AI20 小时前
(一)初识 Stable Diffusion 3.5 —— 下一代多模态架构详解
人工智能·stable diffusion
Master_oid20 小时前
机器学习26:增强式学习(Deep Reinforcement Learn)①
人工智能·学习·机器学习
鲨莎分不晴20 小时前
从 0 实现一个 Offline RL 算法 (以 IQL 为例)
人工智能·深度学习·机器学习
rayufo20 小时前
深度学习图像复原论文《SwinIR: Image Restoration Using Swin Transformer》解读及其代码实现
人工智能·深度学习·transformer
万俟淋曦20 小时前
【论文速递】2025年第42周(Oct-12-18)(Robotics/Embodied AI/LLM)
人工智能·ai·机器人·大模型·论文·robotics·具身智能
hero_heart20 小时前
opencv和摄影测量坐标系的转换
人工智能·opencv·计算机视觉
Java后端的Ai之路20 小时前
【分析式AI】-时间序列模型一文详解
人工智能·aigc·时间序列·算法模型·分析式ai
AI即插即用20 小时前
即插即用系列 | CMPB PMFSNet:多尺度特征自注意力网络,打破轻量级医学图像分割的性能天花板
网络·图像处理·人工智能·深度学习·神经网络·计算机视觉·视觉检测
love530love20 小时前
在 PyCharm 中配置 x64 Native Tools Command Prompt for VS 2022 作为默认终端
ide·人工智能·windows·python·pycharm·prompt·comfyui