阿里云官方鏡像站:ROS2源
https://developer.aliyun.com/mirror/?utm_content=g_1000303593
一、準備工作
創建工作空間,即編寫代碼的位置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:
rosidl_default_generators
rosidl_default_runtime
rosidl_interface_packages
最后回到工作空間路徑下編譯構建 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
void add(const std::shared_ptr request, // CHANGE
std::shared_ptr 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 node = rclcpp::Node::make_shared("add_three_ints_server"); // CHANGE
rclcpp::Service::SharedPtr service = // CHANGE
node->create_service("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
#include
#include
#include
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 node = rclcpp::Node::make_shared("add_three_ints_client");
rclcpp::Client::SharedPtr client =
node->create_client("add_three_ints");
auto request = std::make_shared();
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
// #include
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文件:
client
0.0.0
TODO: Package description
zhi
TODO: License declaration
ament_cmake
rclcpp
tutorial_interfaces
ament_lint_auto
ament_lint_common
ament_cmake
然后回到工作空間執行編譯構建命令:
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
本文轉自:https://www.cnblogs.com/huaibin/p/15423963.html
編輯:fqj
評論
查看更多