2019/09/12

google testでrosパッケージの単体テストを行う方法

背景


rosパッケージを作成する際に、品質担保のために通常単体テストを行う。
googleテストの使用方法やCMakeListsの書き方を備忘録として記載する。

記事の目的


google testでrosパッケージの単体テストを行う

google test


google testでrosパッケージの単体テストを行う方法について記載する。

google testとは

google testは、google社が提供するオープンソースのテストフレームワークである。

特徴

  • マクロを使用してテストケースを容易に記述できる
  • rosの標準のテストフレームワークである

テンプレートのパッケージ構成

google testでrosパッケージの単体テストを行うrosパッケージの構成を記載する。
$ tree
.
├── CMakeLists.txt -> /opt/ros/kinetic/share/catkin/cmake/toplevel.cmake
└── sample_pkg                # google testでテストを行うサンプルパッケージ
     ├── CMakeLists.txt       # CMakeListsファイル
     ├── include
     │   └── sample_pkg
     │       └── sample.hpp  # headerファイル
     ├── launch
     │   └── sample.launch   #(オプション)launchファイル
     ├── package.xml          # Packageファイル
     ├── src
     │   ├── main.cpp        # ソースファイル(main関数のみ)
     │   └── sample.cpp      # ソースファイル(その他関数, クラス)
     └── test
          └── utest.cpp       # 単体テストファイル

テンプレートのheaderファイルとソースファイル

テンプレートのheaderファイルとソースファイルを記載する。
// sample.hpp
#include <ros/ros.h>

namespace sample
{
    int  func1(int a, int b);
    bool func2(int c, int d);
    class class3
    {
        public:
            class3();
            ~class3();
            void func4(int e);
            void func5(int &f);
        protected:
            int internal_variable_ = 0;
    };
};

// sample.cpp
#include <sample_pkg/sample.hpp>

int sample::func1(int a, int b)
{
    return a+b;
}

bool sample::func2(int a, int b)
{
    if (a == b)
    {
        return true;
    }
    return false;
}

sample::class3::class3()
{}

sample::class3::~class3()
{}

void sample::class3::func4(int e)
{
    internal_variable_ = e;
}

void sample::class3::func5(int &f)
{
    f = internal_variable_;
}

// main.cpp
#include <sample_pkg/sample.hpp>

int main(int argc, char **argv){
    int A = sample::func1(1,3);
    
    return A;
}


テンプレートの単体テストファイル

テンプレートの単体テストファイルを記載する。
// utest.cpp
#include <sample_pkg/sample.hpp>
#include <gtest/gtest.h>

// sample::func1のテスト
TEST(Factorial1Test, func1Test)
{
    int A = sample::func1(1,2);
    EXPECT_EQ(3, A); // A == 3 かチェック
}

// sample::func2のテスト
TEST(Factorial2Test, func2Test)
{
    EXPECT_TRUE(sample::func2(1,1)); // a == bの時、戻り値がtrueかチェック
    EXPECT_FALSE(sample::func2(1,2)); // a != bの時、戻り値がfalseかチェック
}

// sample::class3のテスト
TEST(Class3Test, func4_5Test1)
{
    sample::class3 c;
    int a;
    c.func5(a);
    EXPECT_EQ(0, a); // 初期状態でc.func5(a)のa==0かチェック
}

// sample::class3のテスト
TEST(Class3Test, func4_5Test2)
{
    sample::class3 c;
    int a = 4;
    int b = 0;
    c.func4(a);
    c.func5(b);
    EXPECT_EQ(a, b); // c.func4(a)を行った後、c.func5(b)のb==aかチェック
}

// Run all the tests that were declared with TEST()
int main(int argc, char **argv){
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

テンプレートのCMakeListsファイルとPackageファイル

テンプレートのCMakeListsファイルとPackageファイルを記載する。
# CMakeLists.txt
cmake_minimum_required(VERSION 2.8.3)
project(sample_pkg)

## Compile as C++11, supported in ROS Kinetic and newer
add_compile_options(-std=c++11 -Wall -g -O3)

## Find catkin macros and libraries
find_package(catkin REQUIRED COMPONENTS
             roscpp
)

###################################
## catkin specific configuration ##
###################################

## Declare things to be passed to dependent projects
catkin_package(
   INCLUDE_DIRS include
   LIBRARIES sample_pkg
)

###########
## Build ##
###########

## Specify additional locations of header files
include_directories(include
                    /usr/local/include
                    ${catkin_INCLUDE_DIRS}
)

## Declare a C++ library
add_library(${PROJECT_NAME}_lib
            src/sample.cpp
)

## Declare a C++ executable
add_executable(${PROJECT_NAME}
               src/main.cpp
)

## Specify libraries to link a library or executable target against
target_link_libraries(${PROJECT_NAME}_lib
                      ${catkin_LIBRARIES}
)

target_link_libraries(${PROJECT_NAME}
                      ${PROJECT_NAME}_lib
                      ${catkin_LIBRARIES}
)

#############
## Install ##
#############

## Mark executables and/or libraries for installation
install(TARGETS ${PROJECT_NAME}
        ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
        LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
        RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

## Mark cpp header files for installation
install(DIRECTORY include/${PROJECT_NAME}/
        DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
        FILES_MATCHING PATTERN "*.hpp"
)

#############
## Testing ##
#############

## Add gtest based cpp test target and link libraries
catkin_add_gtest(${PROJECT_NAME}_test test/utest.cpp)
target_link_libraries(${PROJECT_NAME}_test
                      ${PROJECT_NAME}_lib
                      ${catkin_LIBRARIES}
)

<!-- package.xml -->
<?xml version="1.0"?>
<package format="2">
  <name>sample_pkg</name>
  <version>0.0.0</version>
  <description>The sample package</description>

  <!-- One maintainer tag required, multiple allowed, one person per tag -->
  <maintainer email="xxx@yyy.zz">EmptySet</maintainer>

  <!-- One license tag required, multiple allowed, one license per tag -->
  <!-- Commonly used license strings: -->
  <!--   BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3 -->
  <license>MIT</license>

  <!-- Url tags are optional, but multiple are allowed, one per tag -->
  <!-- Optional attribute type can be: website, bugtracker, or repository -->
  <url type="website">https://ittechnicalmemos.blogspot.com</url>

  <!-- Author tags are optional, multiple are allowed, one per tag -->
  <!-- Authors do not have to be maintainers, but could be -->
  <author email="xxx@yyy.zz">EmptySet</author>

  <!-- The *depend tags are used to specify dependencies -->
  <!-- Dependencies can be catkin packages or system dependencies -->
  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>roscpp</build_depend>
  <build_export_depend>roscpp</build_export_depend>
  <exec_depend>roscpp</exec_depend>
</package>

テンプレートの単体テスト実行方法

テンプレートの単体テスト実行方法及び結果を記載する。
$ catkin_make run_tests
...
- run_tests.py: execute commands
  /home/emptySet/work_space/devel/lib/sample_pkg/sample_pkg_test --gtest_output=xml:/home/emptySet/work_space/build/test_results/sample_pkg/gtest-sample_pkg_test.xml
[==========] Running 4 tests from 3 test cases.
[----------] Global test environment set-up.
[----------] 1 test from Factorial1Test
[ RUN      ] Factorial1Test.func1Test
[       OK ] Factorial1Test.func1Test (0 ms)
[----------] 1 test from Factorial1Test (0 ms total)

[----------] 1 test from Factorial2Test
[ RUN      ] Factorial2Test.func2Test
[       OK ] Factorial2Test.func2Test (0 ms)
[----------] 1 test from Factorial2Test (0 ms total)

[----------] 2 tests from Class3Test
[ RUN      ] Class3Test.func4_5Test1
[       OK ] Class3Test.func4_5Test1 (0 ms)
[ RUN      ] Class3Test.func4_5Test2
[       OK ] Class3Test.func4_5Test2 (0 ms)
[----------] 2 tests from Class3Test (0 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 3 test cases ran. (0 ms total)
[  PASSED  ] 4 tests.
-- run_tests.py: verify result "/home/emptySet/work_space/build/test_results/sample_pkg/gtest-sample_pkg_test.xml"
...

テスト結果がNGの場合は、下記のように表示される。
$ catkin_make run_tests
...
/home/emptySet/work_space/src/sample_pkg/test/utest.cpp:31: Failure
Value of: a
  Actual: 4
Expected: 2
[  FAILED  ] Class3Test.func4_5Test2 (0 ms)
...
[  FAILED  ] 1 test, listed below:
[  FAILED  ] Class3Test.func4_5Test2

 1 FAILED TEST
...

備考

  • google testのマクロはここを参照。

まとめ


  • google testでrosパッケージの単体テストを行う方法を調査、記載した

参考文献



変更履歴


  1. 2019/09/12: 新規作成

0 件のコメント:

コメントを投稿

MQTTの導入

背景 IoTデバイスの接続環境構築のため、MQTT(mosquitto)の導入を行った。 記事の目的 MQTT(mosquitto)をUbuntuに導入する mosquitto ここではmosquittoについて記載する。 MQTT MQTT(Message Qu...