• ROS2中使用Gtes示例


    一、准备工作

    创建工作空间,即编写代码的位置mkdir -p dev_ws/src

    进入 dev_ws/src 路径下:

    先创建依赖包 tutorial_interfaces:

    ros2 pkg create --build-type ament_cmake tutorial_interfaces
    

    进入 dev_ws/src/tutorial_interfaces ,然后创建msgsrv目录存放.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
    a: %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.hparams.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.cppmain.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.cppmain.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
    

    程序封装改写:

    如果不知道返回类型,直接断点下去看下类型:

  • 相关阅读:
    Vue.js中学习使用Vuex详解
    vuex存储和本地存储(localstorage、sessionstorage)的区别
    Java 编译与反编译
    Vue导航守卫beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave详解
    Vue生命周期简介和钩子函数
    微信开发----被动回复用户消息
    C#4.0 System.Dynamic
    Mvc5 控制器,视图简单说明
    JQuery 禁用后退按钮
    防止用户多次点击
  • 原文地址:https://www.cnblogs.com/huaibin/p/15423963.html
Copyright © 2020-2023  润新知