MyEnigma

とある自律移動システムエンジニアのブログです。#Robotics #Programing #C++ #Python #MATLAB #Vim #Mathematics #Book #Movie #Traveling #Mac #iPhone

ROS C++コード用テストライブラリgtestの使い方(日本語訳)


元記事:gtest - ROS Wiki http://wiki.ros.org/gtest

gtestのインストール方法

ROS1.0(Box Turtle)や1.2 (C Turtle)では

gtestはデフォルトインストールパッケージとして

ROSをインストールした時にインストールされます。

それ以降のROSのバージョンの場合、

gtestはrosdepを使って下記の方法でインストールすることができます。

How to install gtest - ROS Answers: Open Source Q&A Forum http://answers.ros.org/question/114121/how-to-install-gtest/

rosdep - ROS Wiki http://wiki.ros.org/rosdep


Google Test (gtest)について

ROSのコミュニティではC++ソフトのユニットテストを実施するために、

Google Test (gtest)を使用しています。

Google TestはGoogleが開発しているC++ソフト用

ユニットテストフレームワークであり、

マルチプラットフォームで、

テストコードのメンテナンス性が高いという特徴があります。

Google Testの公式ドキュメントは下記の通りです。

googletest - Google C++ Testing Framework - Google Project Hosting https://code.google.com/p/googletest/

Wiki Pages - googletest - Google C++ Testing Framework - Google Project Hosting https://code.google.com/p/googletest/w/list

A quick introduction to the Google C++ Testing Framework http://www.ibm.com/developerworks/aix/library/au-googletestingframework.html

入門ガイド ― Google Test ドキュメント日本語訳 http://opencv.jp/googletestdocs/primer.html#primer-basic-concepts


これらの資料には、Google Testによる

ROSコードのユニットテストを書いたり、実行したり

する時のヒントや例題が記されています。

C++以外の言語のテストに関する

ポリシーや方法に関しては、下記のリンクを参照してください。

UnitTesting - ROS Wiki http://wiki.ros.org/UnitTesting

ROSにおけるUnit/Integration/Regression Test (日本語訳) - MY ENIGMA http://d.hatena.ne.jp/meison_amsl/20140110/1389362208



コードの構造

コード管理を簡単にするために、

あるパッケージのテストプログラムは、

サブディレクトリである”test”フォルダに置いて下さい。

シンプルなパッケージでは、テストファイルは一つで良い場合が多いため、

テスト用のファイルはtest/utest.cppのように配置されます。


テストコードの書き方

一般的なテストコードの構造は下記の通りです。

// Bring in my package's API, which is what I'm testing
#include "foo/foo.h"
// Bring in gtest
#include <gtest/gtest.h>

// Declare a test
TEST(TestSuite, testCase1)
{
    <test things here, calling EXPECT_* and/or ASSERT_* macros as needed>
}

// Declare another test
TEST(TestSuite, testCase2)
{
    <test things here, calling EXPECT_* and/or ASSERT_* macros as needed>
}

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

テストの名前規則

それぞれのの個々のテストは”テストケース”、

それらのテストケースをグループ化すると、

”テストスイート(Test suites)”と呼ばれます。

どのようなテストコードをテストスイートと呼び、

どのようなテストスイートを使用するかは、

ユーザ次第ですが、

多くのパッケージは最低一つのテストスイートが必要であり、

問題がなければ、より一層のテストスイートを利用することができます。

命名規則に関しては、

テストスイート: CamelCased 

テストケース:  camelCased

のようにしてください。


テストのビルド方法と実施方法

あなたのパッケージにテストを追加したいときは、

CMakeLists.txtに下記のコードを追加してください。

catkinの場合

catkin_add_gtest(utest test/utest.cpp)

rosbuildの場合

rosbuild_add_gtest(test/utest test/utest.cpp)


このコマンドによるutestはメインのビルドの間にビルドされ、

実行ファイルに変換されます。

そして実行ファイルはrosbuildの場合、

bin/testに作成されます。

(このbin/testディレクトリはユーザが手動で作成する必要があります。)

テストのみを実行する場合は

./bin/test/utest

でテストのみを実行し、

メイクと同時にテストを実施したい場合は、

make test

で実行できます。

またrosbuild_add_gtest()マクロに関しては、下記の記事を参照してください。

rosbuild/CMakeLists - ROS Wiki http://wiki.ros.org/action/show/rosbuild/CMakeLists?action=show&redirect=CMakeLists



catkinでテストコードを実行したい場合は下記の記事を見てください。

How do I only run tests for only one package? - ROS Answers: Open Source Q&A Forum http://answers.ros.org/question/62583/how-do-i-only-run-tests-for-only-one-package/

またcatkin_add_gtest()マクロに関しては、下記の記事を参照してください。

catkin/CMakeLists.txt - ROS Wiki http://wiki.ros.org/catkin/CMakeLists.txt

注意点1

gtestに関する依存関係はマニフェストの中には書かないようにしてください。

rosbuild_add_gtestマクロのフラグによって、自動的に依存関係が入力されます。

注意点2

もし、gtestに対して実行ファイルをビルドしたい場合には、

一つのテストファイルとして宣言するのではなく、

rosbuild_add_gtest_build_flags()の後に、

rosbuild_add_executable()

を使用することでビルドしてください。

詳細は下記の記事を参照してください。

rosbuild/CMakeLists - ROS Wiki http://wiki.ros.org/action/show/rosbuild/CMakeLists?action=show&redirect=CMakeLists

注意点3

Ubuntu 11.10を使用している場合、

"undefined reference to `pthread_getspecific'"

というエラーが出ることがあります。

この問題を解決するためには、

コンパイラフラグとして、 “-pthread”を追加するために、

rosbuild_add_gtestを呼ぶ前に下記のコマンドを追加してください。

SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")

テスト結果

作成したテストを実行すると、下記のような結果がコンソールに出力されます。

[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from MapServer
[ RUN ] MapServer.loadValidPNG
[ OK ] MapServer.loadValidPNG
[ RUN ] MapServer.loadValidBMP
[ OK ] MapServer.loadValidBMP
[ RUN ] MapServer.loadInvalidFile
[ OK ] MapServer.loadInvalidFile
[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran.
[ PASSED ] 3 tests.

make testを使用してプログラムを実行した場合、

それぞれのテストでは下記の場所にXMLファイルを生成します。

・ROS cturtleの場合

$ROS_ROOT/test/test_results/

・Diamondback以降

~/.ros/test_results


自分のシステムにおいて、どこにXMLファイルが

生成されるのかを確認したい場合は、

下記のコマンドを起動すると確認できます。

rosrun rosunit test_results_dir.py

生成されたXMLファイルの例は下記の通りです。

<?xml version="1.0" encoding="UTF-8"?>
<testsuite tests="3" failures="1" disabled="0" errors="0" time="25" name="AllTests">
  <testsuite name="MapServer" tests="3" failures="1" disabled="0" errors="0" time="25">
    <testcase name="loadValidPNG" status="run" time="24" classname="MapServer">
      <failure message="/Users/gerkey/code/ros-pkg/world_models/map_server/test/utest.cpp:56&#x0A;Failed&#x0A;Uncaught exception : This is OK on OS X" type=""/>
    </testcase>
    <testcase name="loadValidBMP" status="run" time="0" classname="MapServer" />
    <testcase name="loadInvalidFile" status="run" time="1" classname="MapServer" />
  </testsuite>
</testsuite>

将来的には、このXMLファイルを読み込んで

結果を表示するウェブベースのツールを作成する予定です。


コード例

シンプルな例:関数呼び出し

下記の例は、math_utilsパッケージのテストコードです。

このコードをみれば、

様々な関数に対する適切な結果をテストする方法がわかると思います。

下記の例で注意してもらいたいことは、

ASSERT_* macrosではなく、EXPECT_* macrosを使用していることです。

ASSERT_* macrosはテスト失敗を保存すると、

すぐにテストプロセスから抜けてしまいますが、

EXPECT_* macrosは、テスト失敗を保存した後にテストを継続してくれます。

一般的に、すべてのテストを実施してもらいたいはずなので、

EXPECT_* macrosの振る舞いの方が適している場合が多いのです。
しかし、一つ前のテストが成功した場合のみ、

次のテストをしたい時は、ASSERT_* macrosが適しているでしょう。

#include "math_utils/MathExpression.h"
#include "math_utils/math_utils.h"
#include <gtest/gtest.h>

#define TEST_EXPRESSION(a) EXPECT_EQ((a), meval::EvaluateMathExpression(#a))

TEST(MathExpressions, operatorRecognition){
  EXPECT_TRUE(meval::ContainsOperators("+"));
  EXPECT_TRUE(meval::ContainsOperators("-"));
  EXPECT_TRUE(meval::ContainsOperators("/"));
  EXPECT_TRUE(meval::ContainsOperators("*"));
  EXPECT_FALSE(meval::ContainsOperators("1234567890qwertyuiop[]asdfghjkl;'zxcvbnm,._=?8")); 
}

TEST(MathExpressions, basicOperations){
EXPECT_EQ(5, meval::EvaluateMathExpression("2+3"));
EXPECT_EQ(5, meval::EvaluateMathExpression("2 + 3"));
EXPECT_EQ(10, meval::EvaluateMathExpression("20/2"));
EXPECT_EQ(-4, meval::EvaluateMathExpression("6 - 10"));
EXPECT_EQ(24, meval::EvaluateMathExpression("6 * 4"));
}

TEST(MathExpressions, complexOperations){
  TEST_EXPRESSION(((3 + 4) / 2.0) + 10);
  TEST_EXPRESSION(7 * (1 + 2 + 3 - 2 + 3.4) / 12.7);
  TEST_EXPRESSION((1 + 2 + 3) - (8.0 / 10)); 
}

TEST(MathExpressions, UnaryMinus){
  TEST_EXPRESSION(-5);
}

TEST(MathExpressions, badInput){
  //TODO - figure out what good error behavior is and test for it properly
  //EXPECT_EQ(0, meval::EvaluateMathExpression("4.1.3 - 4.1"));
  //EXPECT_EQ(0, meval::EvaluateMathExpression("4.1.3"));
}

TEST(MathUtils, basicOperations){
  EXPECT_EQ(math_utils::clamp<int>(-10, 10, 20), 10);
  EXPECT_EQ(math_utils::clamp<int>(15, 10, 20), 15);
  EXPECT_EQ(math_utils::clamp<int>(25, 10, 20), 20);
}

int main(int argc, char **argv){
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
例外の取扱い方

以前のgtestは例外をサポートしていませんでしたが、

ROS1.1に含まれる新しいバージョンのgtestは例外をサポートしています。

詳しくは下記の資料を見てください。

AdvancedGuide - googletest - Google C++ Testing Framework - Google Project Hosting https://code.google.com/p/googletest/wiki/AdvancedGuide#Exception_Assertions


永続データのためのテストフィクスチャの使い方

testing::Testを継承したクラスを使用することで、

テストフィクスチャを作成し、

永続データを作成することができます。

テストフィクスチャを使用したテストの場合は TESTマクロではなく、

TEST_F macroを使用してください。

テストフィクスチャを使用したテストに関しては下記の資料を参考にしてください。

Primer - googletest - Getting started with Google C++ Testing Framework - Google C++ Testing Framework - Google Project Hosting https://code.google.com/p/googletest/wiki/Primer


gtest使用時の注意点

上記の方法通りにテストコードを書いて、

コンパイルしても、

下記のようなエラーが出るときは、

/usr/include/gtest/internal/gtest-port.h:1365: undefined reference to `pthread_key_create'

下記のように、rosbuild_add_gtestの前で

コンパイルフラグを設定する必要があるようです。

# For Unit test
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
rosbuild_add_gtest(test/utest test/utest.cpp)

この問題は、Ubuntu 11.10を使用した場合の問題として、説明されていますが、

自分の環境、Ubuntu 12.04、ROS Fuerteでも、問題として再現したので、

気をつけて下さい。

MyEnigma Supporters

もしこの記事が参考になり、

ブログをサポートしたいと思われた方は、

こちらからよろしくお願いします。

myenigma.hatenablog.com