ROS2中使用Gtest示例
一、准备工作
创建工作空间,即编写代码的位置mkdir -p dev_ws/src
进入 dev_ws/src
路径下:
先创建依赖包 tutorial_interfaces:
ros2 pkg create --build-type ament_cmake tutorial_interfaces
进入 dev_ws/src/tutorial_interfaces
,然后创建msg
和srv
目录存放.msg文件和.srv文件:
mkdir msg
mkdir srv
进入dev_ws/src/tutorial_interface/msg
目录,新建 Num.msg
文件:
int64 num
进入 dev_ws/src/tutorial_interface/srv
目录,新建 AddThreeInts.srv
文件:
int64 a
int64 b
int64 c
---
int64 sum
然后编辑CMakeLists.txt
文件:
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Num.msg"
"srv/AddThreeInts.srv"
)
继续编辑package.xml
:
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
最后回到工作空间路径下编译构建 tutorial_interfaces 包:
colcon build --packages-select tutorial_interfaces
以上操作步骤详细链接:
Creating custom ROS 2 msg and srv files — ROS 2 Documentation: Galactic documentation
然后在工作空间的src路径下创建自己的包 service,并指定依赖的包:
ros2 pkg create --build-type ament_cmake service --dependencies rclcpp tutorial_interfaces
在 dev_ws/src/service/src
目录下新建service.cpp
#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/srv/add_three_ints.hpp" // CHANGE
#include <memory>
void add(const std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Request> request, // CHANGE
std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Response> response) // CHANGE
{
response->sum = request->a + request->b + request->c; // CHANGE
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld" " c: %ld", // CHANGE
request->a, request->b, request->c); // CHANGE
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_server"); // CHANGE
rclcpp::Service<tutorial_interfaces::srv::AddThreeInts>::SharedPtr service = // CHANGE
node->create_service<tutorial_interfaces::srv::AddThreeInts>("add_three_ints", &add); // CHANGE
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add three ints."); // CHANGE
rclcpp::spin(node);
rclcpp::shutdown();
}
二、测试代码编写
进入工作空间的src路径下,执行下面命令生成待测模块client
:
ros2 pkg create --build-type ament_cmake client --dependencies rclcpp tutorial_interfaces
进入client路径下,分别新建src,test目录,include目录默认已经存在。
include目录下文件:client.h
和params.h
// client.h
#ifndef CLIENT_H
#define CLIENT_H
class ClientHandler
{
public:
ClientHandler();
~ClientHandler();
bool sendParams(int argc, char **argv);
};
#endif
// params.h
#ifndef PARAMS_H
#define PARAMS_H
extern int my_argc;
extern char** my_argv;
#endif
src目录下文件:client.cpp
和main.cpp
// client.cpp
#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/srv/add_three_ints.hpp"
#include "../include/client.h"
#include <chrono>
#include <cstdlib>
#include <memory>
#include<vector>
using namespace std;
using namespace std::chrono_literals;
// 构造函数
ClientHandler::ClientHandler(){
}
// 析构函数
ClientHandler::~ClientHandler(){
}
// 普通函数——发送参数
bool ClientHandler::sendParams(int argc, char **argv)
{
rclcpp::init(argc, argv);
if (argc != 4) {
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_three_ints_client X Y Z");
return false;
}
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_client");
rclcpp::Client<tutorial_interfaces::srv::AddThreeInts>::SharedPtr client =
node->create_client<tutorial_interfaces::srv::AddThreeInts>("add_three_ints");
auto request = std::make_shared<tutorial_interfaces::srv::AddThreeInts::Request>();
request->a = atoll(argv[1]);
request->b = atoll(argv[2]);
request->c = atoll(argv[3]);
while (!client->wait_for_service(1s)) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return false;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
}
auto result = client->async_send_request(request);
// Wait for the result.
if (rclcpp::spin_until_future_complete(node, result) ==
rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
} else {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_three_ints");
}
rclcpp::shutdown();
return true;
}
// main.cpp
#include "../include/client.h"
int main(int argc, char **argv){
// 注意这里: C++ 编译器把不带参数的构造函数优先认为是一个函数声明
ClientHandler client{};
client.sendParams(argc, argv);
}
test目录下文件:clientTest.cpp
和main.cpp
// clientTest.cpp
#include "gtest/gtest.h"
#include "../include/client.h"
#include "../include/params.h"
TEST(ClientHandler, sendParams)
{
// 测试的时候的交互方式也不能改变,既然client实际的效果是在命令行输入参数,
// 那这里也是这样的效果
ClientHandler client{};
EXPECT_EQ(true, client.sendParams(my_argc, my_argv));
}
// main.cpp
#include <gtest/gtest.h>
// #include <gmock/gmock.h>
int my_argc;
char** my_argv;
int main(int argc, char** argv) {
// ::testing::InitGoogleMock(&argc, argv);
// 注意这里使用的是Gtest,不是Gmock
::testing::InitGoogleTest(&argc, argv);
// Runs all tests using Google Test.
my_argc = argc;
my_argv = argv;
return RUN_ALL_TESTS();
}
CMakeLists.txt
文件:
cmake_minimum_required(VERSION 3.8)
project(client)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED)
set(SRC
src/client.cpp
src/main.cpp
)
add_executable(client
${SRC}
)
ament_target_dependencies(client
rclcpp tutorial_interfaces)
# 5. 添加当前项目中的头文件 注意有顺序的要求,不能乱
target_include_directories(client
PRIVATE
${PROJECT_SOURCE_DIR}/include
)
# 如果是测试代码
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# 加入gtest包
find_package(ament_cmake_gtest REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
# set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
# set(ament_cmake_cpplint_FOUND TRUE)
set(TEST
test/main.cpp
test/clientTest.cpp
)
# 生成加入gtest的test执行文件。${PROJECT_NAME}_test为自定义的test执行文件名称;test/demo_test.cpp为test源码路径
# 注意这里导包的时候,不再需要将 .h 文件导入进来,因为在 client.cpp中已经导入了我们需要使用到的.h文件
# 另外,注意这里不能导入开发代码中的 main.cpp,因为已经有了一个测试的main.cpp
ament_add_gtest(${PROJECT_NAME}_test ${TEST} src/client.cpp)
# 务必注意这里需要添加的依赖包
ament_target_dependencies(${PROJECT_NAME}_test rclcpp tutorial_interfaces)
install(TARGETS
${PROJECT_NAME}_test
# 将生成的test执行文件安装到DESTINATION后的路径下
DESTINATION lib/${PROJECT_NAME})
ament_lint_auto_find_test_dependencies()
endif()
install(TARGETS
client
DESTINATION lib/${PROJECT_NAME})
# 设置编译构建类型为 调试 模式
set(CMAKE_BUILD_TYPE Debug)
# 生成覆盖率文件
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage")
ament_package()
package.xml
文件:
<?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>client</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="zhi@todo.todo">zhi</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>tutorial_interfaces</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
然后回到工作空间执行编译构建命令:
colcon build --packages-select client
进入到 dev_ws/build/client
路径下,找到client_test
可执行文件即为生成的测试文件
在另外一个终端启动service包:
ros2 run cpp_srvcli server
执行
./client_test 23 3 4
运行测试文件即可。
生成覆盖率的脚本:
#!/usr/bin/bash
echo "begin gen coverage file ..."
lcov --no-external --capture --initial --directory . --output-file /home/zhi/ros2-gtest-gmock/info/ros2_base.info
cd /home/zhi/ros2-gtest-gmock/build/client;./client_test 45 56 56;cd /home/zhi/ros2-gtest-gmock
# 注意下面的=号两侧是不可以有空格的,这是个大坑
current_path=$(pwd)
echo "当前目录是:" $current_path
lcov --no-external --capture --directory . --output-file /home/zhi/ros2-gtest-gmock/info/ros2.info
lcov --add-tracefile /home/zhi/ros2-gtest-gmock/info/ros2_base.info --add-tracefile /home/zhi/ros2-gtest-gmock/info/ros2.info --output-file /home/zhi/ros2-gtest-gmock/info/ros2_coverage.info
mkdir -p coverage && genhtml /home/zhi/ros2-gtest-gmock/info/ros2_coverage.info --output-directory coverage
程序封装改写:
如果不知道返回类型,直接断点下去看下类型: