Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ROS2自律走行実現に向けて2 / Autonomous driving realization by ROS2 test framework

fixstars
December 01, 2022

ROS2自律走行実現に向けて2 / Autonomous driving realization by ROS2 test framework

2022年11月30日に開催した「ROS2自律走行実現に向けて~次世代ロボット開発フレームワークROS2のテストフレームワーク徹底理解~」の当日資料です。

fixstars

December 01, 2022
Tweet

More Decks by fixstars

Other Decks in Programming

Transcript

  1. Copyright © Fixstars Group ROS2自律走行実現に向けて 次世代ロボット開発フレームワーク ROS2のテストフレームワーク徹底理解

  2. Copyright © Fixstars Group 本日のAgenda ⚫ はじめに ⚫ フィックスターズのご紹介 ⚫

    テストの必要性 ⚫ ROS2で可能なテスト/lint ⚫ サンプルアプリでのテスト実装 ⚫ その他 TIPS 2
  3. Copyright © Fixstars Group はじめに

  4. Copyright © Fixstars Group 本セミナーの位置づけ ⚫ ウェビナー「ROS2自律走行実現に向けて」シリーズでは、 自律走行する車両型ロボットの実現に向け、 ROS2を使った開発に関連する、様々な情報を発信しています ⚫

    vol.1(発表資料) ◦ ROS1上で開発した資産の活用 ◦ 自己位置推定パッケージの CUDA高速化 ⚫ vol.2(発表資料) ◦ パッケージ開発に欠かせないビルドシステムの解説 ⚫ 今回の内容 ◦ 品質向上に欠かせないテストフレームワークの解説 ⚫ こんな方に向いています ◦ ROS2 の開発をしたことがあり、ソフトウェアの品質を向上させたいとお考えの方 4
  5. Copyright © Fixstars Group 発表者紹介 5 冨田 明彦 ソリューションカンパニー 執行役員

    2008年に入社。金融、医療業界において、ソ フトウェア高速化業務に携わる。その後、新規 事業企画、半導体業界の事業を担当し、現職。 青木 修平 ソリューション第3事業部 シニアエンジニア 2018年に入社。主に ADAS 向けの画像処理ア ルゴリズムの開発や高速化、シミュレーション 環境構築を担当。
  6. Copyright © Fixstars Group フィックスターズの ご紹介

  7. Copyright © Fixstars Group フィックスターズの強み コンピュータの性能を最大限に引き出す、ソフトウェア高速化のエキスパート集団 ハードウェアの知見 アルゴリズム実装力 各産業・研究分野の知見 7

    目的の製品に最適なハードウェアを見抜き、 その性能をフル活用するソフトウェアを開 発します。 ハードウェアの特徴と製品要求仕様に合わ せて、アルゴリズムを改良して高速化を実 現します。 開発したい製品に使える技術を見抜き、実 際に動作する実装までトータルにサポート します。
  8. Copyright © Fixstars Group サービス提供分野 8 半導体 自動車 産業機器 生命科学

    金融 •NAND型フラッシュメモリ向けフ ァームウェア開発 •次世代AIチップの開発環境基盤 •自動運転の高性能化、実用化 •次世代パーソナルモビリティの 研究開発 •Smart Factory実現への支援 •マシンビジョンシステムの高速化 •ゲノム解析の高速化 •医用画像処理の高速化 •AI画像診断システムの研究開発 •デリバティブシステムの高速化 •HFT(アルゴリズムトレード)の高速化
  9. Copyright © Fixstars Group 自動車向けソフトウェア開発 アルゴリズム開発から量産車ターゲット向けの高速化まで、 自動運転の実現に向けた統合的な技術開発を行っています。 ご支援内容 9

  10. Copyright © Fixstars Group 組込み高速化 組込み機器製品の計算処理実装をお手伝いしています。 お客様の課題 組込みシステムの目標性能が達成できない ターゲットデバイスの特性に合わせて、 性能要求を満たしたい

    安価なハードウェアでも処理速度を維持し 製品にかかるコストを下げたい ターゲットデバイスの例 ARM/ TOSHIBA Visconti/ Renesas R-Car/ NXP S32/ Automotive Platform/ CEVA-XM6/ Texas Instruments C6000/ Cadence Vision DSP Family など システム設計コンサルティング ハードウェア選定を含めたシステム設計のご提案 アルゴリズムの改善と移植 既存アルゴリズムを改善して計算を高速化 組込みアルゴリズム開発 ターゲットデバイス向けに最適化されたアルゴリズムを実装 ご支援内容 最適化方針のご提案 ボトルネック調査、最適化に向けた検討 10
  11. Copyright © Fixstars Group サービス領域一覧 様々な領域でソフトウェア高速化サービスを提供しています。大量データの高速処理は、 お客様の製品競争力の源泉となっています。 11 組込み高速化 画像処理・アルゴリズム

    開発 分散並列システム開発 GPU向け高速化 FPGAを活用した システム開発 量子コンピューティング AI・深層学習 自動車向け ソフトウェア開発 フラッシュメモリ向けフ ァームウェア開発
  12. Copyright © Fixstars Group ROS2の テストフレームワーク 徹底理解

  13. Copyright © Fixstars Group 本セミナーのねらい ⚫ ソフトウェアの品質向上にはテストは欠かせないが、ROS2ではどういった テストが可能なのか、どのように実現されているか、どのように利用すれば 良いかの情報が少ない ⚫

    そこで、本セミナーではサンプルを交えつつ、ROS2におけるテストについ て網羅的な解説を行う ⚫ サンプルは ROS2 Humble で動作確認済み 13
  14. Copyright © Fixstars Group アジェンダ ⚫ テストとは ⚫ ROS2で可能なテスト/lint ⚫

    サンプルアプリでのテスト実装 ⚫ その他TIPS 14
  15. Copyright © Fixstars Group テストとは

  16. Copyright © Fixstars Group テストとは ⚫ ソフトウェアが正しく作られているか確認する作業 ⚫ テストの分類 ◦

    機能テスト ▪ 単体テスト、結合テスト、システムテスト、etc. ◦ 非機能テスト ▪ パフォーマンステスト、ストレステスト、保守性テスト、etc. ⚫ テストがないと何が困る? ◦ 関数やシステムが正しく動作しているか確認できない ⚫ とはいえ、手作業でテストを行うのは大変 ◦ → テストフレームワークを使って自動化したい 16
  17. Copyright © Fixstars Group テストとは ⚫ ソフトウェアが正しく作られているか確認する作業 ⚫ テストの分類 ◦

    機能テスト ▪ 単体テスト、結合テスト、システムテスト、etc. ◦ 非機能テスト ▪ パフォーマンステスト、ストレステスト、保守性テスト、etc. ⚫ テストがないと何が困る? ◦ 関数やシステムが正しく動作しているか確認できない ⚫ とはいえ、手作業でテストを行うのは大変 ◦ → テストフレームワークを使って自動化したい 17 ROS2におけるテスト/テストフレームワークの使い方を解説する
  18. Copyright © Fixstars Group ROS2でのテストの実行方法 ⚫ ros2 では colcon test

    でテストを実行する ◦ テストに成功した場合 18
  19. Copyright © Fixstars Group ROS2でのテストの実行方法 ⚫ ros2 では colcon test

    でテストを実行する ◦ テストに失敗した場合 19
  20. Copyright © Fixstars Group ROS2でのテストの実行方法 ⚫ colcon test-result で成否結果を得られる 20

    終了ステータスが非ゼロであれば 失敗
  21. Copyright © Fixstars Group ROS2でのテストの実行方法 ⚫ colcon test-result で成否結果を得られる 21

    XML形式でレポートを得られる
  22. Copyright © Fixstars Group ROS2でのテストの実行方法 ⚫ 詳細を見る時は colcon test-result --verbose

    22 …
  23. Copyright © Fixstars Group 23 …

  24. Copyright © Fixstars Group ROS2で可能なテスト /lint

  25. Copyright © Fixstars Group ROS2で可能なテスト/lint ⚫ 公式で提供されているもの 25 ament_cmake ament_python

    ⚫ https://github.com/ament/ament_cmake ⚫ ament_cmakeの拡張としてテストルー ルが存在 ⚫ 基本的に python のテストの仕組みに則っ ている ⚫ pytest と unittest が使用可能 共通 ⚫ https://github.com/ros2/launch ⚫ launch システムを使用したテストフレームワークが存在 ⚫ https://github.com/ament/ament_lint ⚫ lint のための python ツールと ament_cmake 拡張が存在
  26. Copyright © Fixstars Group ROS2で可能なテスト/lint ⚫ 一覧(解説するもの) 26 ament_cmake add_test

    ament_cmake_test ament_cmake_gtest ament_cmake_gmock ament_cmake_pytest ament_python pytest unittest launch integration test launch_testing launch_pytest lint (その2) ament_clang_tidy ament_cppcheck ament_cpplint ament_pclint ament_flake8 ament_mypy ament_pep257 ament_pycodestyle ament_pyflakes lint (その1) ament_xmllint ament_lint_cmake ament_copyright ament_clang_format ament_uncrustify
  27. Copyright © Fixstars Group ROS2で可能なテスト/lint ⚫ 公式で提供されているもの 27 ament_cmake ament_python

    ⚫ https://github.com/ament/ament_cmake ⚫ ament_cmakeの拡張としてテストルー ルが存在 ⚫ 基本的に python のテストの仕組みに則っ ている ⚫ pytest と unittest が使用可能 共通 ⚫ https://github.com/ros2/launch ⚫ launch システムを使用したテストフレームワークが存在 ⚫ https://github.com/ament/ament_lint ⚫ lint のための python ツールと ament_cmake 拡張が存在
  28. Copyright © Fixstars Group ament_cmake でのテスト 名前 ターゲット 概要 add_test

    any ctest (cmake のテストフレームワーク) の機能 ament_cmake でのテストは最終的に add_test の呼び出しとなる ament_cmake_test any ament_cmake が提供するテスト用のマクロ 他のテスト拡張から呼ぶことが想定されているように見える ament_cmake_gtest C/C++ gtest を使うための ament_cmake 拡張 ament_cmake_gmock C/C++ gmock を使うための ament_cmake 拡張 ament_cmake_pytest Python pytest を使うための ament_cmake 拡張 ament_cmake_nose Python テストフレームワークの nose 向けの拡張 nose自体2015年で更新が止まっているので解説を省略 28
  29. Copyright © Fixstars Group ament_cmakeでのテスト • add_test • ament_cmake_test •

    ament_cmake_gtest • ament_cmake_gmock • ament_cmake_pytest 29
  30. Copyright © Fixstars Group 概要 ⚫ CMakeに付属しているテストフレームワークである ctest にテストを登録 ◦

    https://cmake.org/cmake/help/latest/manual/ctest.1.html ◦ https://cmake.org/cmake/help/latest/command/add_test.html ⚫ ament_cmake (純粋なCMakeも) において colcon test は ctest の実行となる ◦ 以降の全ての ament_cmake 向けのテストは最終的に add_test の呼び出しに繋がっている ◦ 任意のテストを作りたい場合も add_test を使えば実行されるようになる ▪ とはいえ、あまり使う場面はなさそう ⚫ CMake記述例 # 成功 add_test( NAME test_success COMMAND bash -c "exit 0" ) # 失敗 add_test( NAME test_failure COMMAND bash -c "exit 1" ) 合否判定は単純で、プログラムの終了コードを見ている • 0 → 成功 • 0 以外 → 失敗 30
  31. Copyright © Fixstars Group 実行例 $ colcon test –packages-select example_ctest

    –event-handlers console_direct+ … test 1 Start 1: test_success 1: Test command: /usr/bin/bash “-c” “exit 0” 1: Test timeout computed to be: 1500 1/2 Test #1: test_success ..................... Passed 0.00 sec test 2 Start 2: test_failure 2: Test command: /usr/bin/bash "-c" "exit 1" 2: Test timeout computed to be: 1500 2/2 Test #2: test_failure .....................***Failed 0.00 sec 50% tests passed, 1 tests failed out of 2 Total Test time (real) = 0.00 sec The following tests FAILED: 2 - test_failure (Failed) Errors while running CTest … 31 ←成功 ←失敗
  32. Copyright © Fixstars Group ament_cmakeでのテスト • add_test • ament_cmake_test •

    ament_cmake_gtest • ament_cmake_gmock • ament_cmake_pytest 32
  33. Copyright © Fixstars Group 概要 ⚫ ament_cmake が提供するテスト用のマクロ ◦ https://github.com/ament/ament_cmake/blob/rolling/ament_cmake_test/cmake/ament_add_test.cmake

    ◦ 自前でテストを実装したい時以外は使う機会はなさそう ⚫ 最終的には add_test の呼び出しになるが、追加の設定やオプションがある ◦ テスト実行を打ち切るまでのタイムアウト (デフォルト60秒) ◦ テストをスキップするかどうかの選択 ◦ etc. (詳しくは CMakeマクロのドキュメントコメントを参照) 33 ← bash ベースのテストフレームワークの bats を使用
  34. Copyright © Fixstars Group CMake記述例 ⚫ bash ベースのテストフレームワークである bats を使用

    34 set(result_dir ${AMENT_TEST_RESULTS_DIR}/${PROJECT_NAME}/test_bats) ament_add_test(test_bats # 実行コマンド COMMAND bats -F junit -o ${result_dir} test.bats # 標準出力の出力先 OUTPUT_FILE ${CMAKE_BINARY_DIR}/ament_cmake_test/test_bats.txt # 実行結果(テストレポート)の保存先 RESULT_FILE ${result_dir}/TestReport-test.bats.xml # テスト実行のタイムアウト TIMEOUT 10 # テスト実行ディレクトリ WORKING_DIRECTORY $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/test> ) CMakeLists.txt
  35. Copyright © Fixstars Group 実行例 $ colcon test --packages-select example_ament_cmake_test

    … $ colcon test-result --verbose build/example_ament_cmake_test/Testing/20221127-1617/Test.xml: 2 tests, 0 errors, 1 failure, 0 skipped - test_bats <<< failure message -- run_test.py: invoking following command in '/workspaces/test-methodlogy/src/one-by- one/03_example_ament_cmake_test/test': - bats -F junit -o /workspaces/test- methodlogy/build/example_ament_cmake_test/test_results/example_ament_cmake_test/test_bats test.bats 1..2 ok 1 Test success in 0sec not ok 2 Test failure in 0sec -- run_test.py: return code 1 -- run_test.py: verify result file '/workspaces/test- methodlogy/build/example_ament_cmake_test/test_results/example_ament_cmake_test/test_bats/TestReport-test.bats.xml' >>> build/example_ament_cmake_test/test_results/example_ament_cmake_test/test_bats/TestReport-test.bats.xml: 2 tests, 0 errors, 1 failure, 0 skipped - test.bats Test failure <<< failure message >>> Summary: 5 tests, 0 errors, 2 failures, 0 skipped 35
  36. Copyright © Fixstars Group C++でテストを記述する場合 add_executable(test_executable test/test.cpp ) ament_add_test(test_doctest COMMAND

    $<TARGET_FILE:test_executable> ) CMakeの方法の則って実行ファイルを 作る 実行ファイルを COMMAND として渡す。 $<TARGET_FILE:test_executable>は Generator Expressions で、 test_executable へのパスを得られる。 36 CMakeLists.txt
  37. Copyright © Fixstars Group ament_cmakeでのテスト • add_test • ament_cmake_test •

    ament_cmake_gtest • ament_cmake_gmock • ament_cmake_pytest 37
  38. Copyright © Fixstars Group 概要 ⚫ gtest (GoogleTest) を使うための ament_cmake

    拡張 ◦ https://github.com/ament/ament_cmake/blob/rolling/ament_cmake_gtest/cmake/ament_add_gtest.cmake ⚫ gtest は C/C++向けのテストフレームワーク ◦ https://github.com/google/googletest ◦ OpenCVにある入門ガイド : http://opencv.jp/googletestdocs/primer.html ⚫ ROS2でC++コードをテストする際はおおよそこれを使うことになる 38
  39. Copyright © Fixstars Group CMake記述例 39 add_library(myadd SHARED src/add.cpp )

    target_include_directories(myadd PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> ) if(BUILD_TESTING) find_package(ament_cmake_gtest REQUIRED) ament_add_gtest(myadd_gtest test/add_test.cpp) target_link_libraries(myadd_gtest myadd) endif() ライブラリ myadd を作成 myadd をテストする myadd_gtest を作成
  40. Copyright © Fixstars Group テストの記述例 40 #pragma once int add(int

    a, int b); #include <example_ament_cmake_gtest/add.hpp> #include <gtest/gtest.h> TEST(add, success) { ASSERT_EQ(add(1, 2), 3); } TEST(add, failure) { ASSERT_EQ(add(1, 2), 4); } example_ament_cmake_gtest/add.hpp test/add_test.cpp 成功するテスト add(1, 2) を呼び出した時の結果は3になるべき 失敗するテスト add(1, 2) を呼び出した時の結果は4になるべき ※テストに失敗した時の表示を見るためのもので実際はこのようなテストは書かない テストの書き方はgtestの作法に従う
  41. Copyright © Fixstars Group 実行例(1/2) 41 $ colcon test --packages-select

    example_ament_cmake_gtest … $ colcon test-result –verbose build/example_ament_cmake_gtest/Testing/20221113-0648/Test.xml: 1 test, 0 errors, 1 failure, 0 skipped - myadd_gtest <<< failure message -- run_test.py: invoking following command in '/workspaces/test-methodlogy/build/example_ament_cmake_gtest': - /workspaces/test-methodlogy/build/example_ament_cmake_gtest/myadd_gtest --gtest_output=xml:/workspaces/test- methodlogy/build/example_ament_cmake_gtest/test_results/example_ament_cmake_gtest/myadd_gtest.gtest.xml Running main() from /opt/ros/humble/src/gtest_vendor/src/gtest_main.cc [==========] Running 2 tests from 1 test suite. [----------] Global test environment set-up. [----------] 2 tests from add [ RUN ] add.success [ OK ] add.success (0 ms) [ RUN ] add.failure /workspaces/test-methodlogy/src/one-by-one/04_example_ament_cmake_gtest/test/add_test.cpp:9: Failure Expected equality of these values: add(1, 2) Which is: 3 4 [ FAILED ] add.failure (0 ms) [----------] 2 tests from add (0 ms total)
  42. Copyright © Fixstars Group 実行例(2/2) 42 [----------] Global test environment

    tear-down [==========] 2 tests from 1 test suite ran. (0 ms total) [ PASSED ] 1 test. [ FAILED ] 1 test, listed below: [ FAILED ] add.failure 1 FAILED TEST -- run_test.py: return code 1 -- run_test.py: inject classname prefix into gtest result file '/workspaces/test- methodlogy/build/example_ament_cmake_gtest/test_results/example_ament_cmake_gtest/myadd_gtest.gtest.xml' -- run_test.py: verify result file '/workspaces/test- methodlogy/build/example_ament_cmake_gtest/test_results/example_ament_cmake_gtest/myadd_gtest.gtest.xml' >>> build/example_ament_cmake_gtest/test_results/example_ament_cmake_gtest/myadd_gtest.gtest.xml: 2 tests, 0 errors, 1 failure, 0 skipped - example_ament_cmake_gtest.add failure <<< failure message /workspaces/test-methodlogy/src/one-by-one/04_example_ament_cmake_gtest/test/add_test.cpp:9 Expected equality of these values: add(1, 2) Which is: 3 4 >>> Summary: 3 tests, 0 errors, 2 failures, 0 skipped
  43. Copyright © Fixstars Group ament_cmakeでのテスト • add_test • ament_cmake_test •

    ament_cmake_gtest • ament_cmake_gmock • ament_cmake_pytest 43
  44. Copyright © Fixstars Group 概要 ⚫ gmock (GoogleMock) を使うための ament_cmake

    拡張 ◦ https://github.com/ament/ament_cmake/blob/rolling/ament_cmake_gmock/cmake/ament_add_gmock.cmake ⚫ gmock は C/C++向けのモックフレームワーク ◦ https://github.com/google/googletest (gtest に含まれる) ◦ OpenCVにある入門ガイド : http://opencv.jp/googlemockdocs/fordummies.html ⚫ ROS2でC++でモックを使ったテストをする際はだいたいこれを使うことに なる 44
  45. Copyright © Fixstars Group CMake記述例 45 add_library(mylib SHARED src/foo.cpp )

    target_include_directories(mylib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> ) if(BUILD_TESTING) find_package(ament_cmake_gmock REQUIRED) ament_add_gmock(my_ament_cmake_gmock test/foo_mock.cpp) target_link_libraries(my_ament_cmake_gmock mylib) endif()
  46. Copyright © Fixstars Group テストの記述例 46 #pragma once class Foo

    { public: virtual bool func(int a) = 0; }; bool bar(Foo * foo); example_ament_cmake_gmock/foo.hpp #include "example_ament_cmake_gmock/foo.hpp" bool bar(Foo *foo) { return foo->func(0); } src/foo.cpp テスト対象は 関数 bar bar は、 1. クラス Foo のインスタンス fooを引数として取り 2. 0 を引数として foo->func を呼び出し 3. foo->func の返り値を bar の返り値として返す bar のテストで確認したいのは、 1. func が呼び出されていること 2. その引数が0であること 3. func が true を返した時、 bar も true を返すこと
  47. Copyright © Fixstars Group テストの記述例 47 #include <example_ament_cmake_gmock/foo.hpp> #include <gmock/gmock.h>

    class MockFoo : public Foo { public: MOCK_METHOD1(func, bool(int)); }; TEST(bar, success) { using namespace testing; MockFoo mock_foo; // mock_fooの使われ方の想定は、 EXPECT_CALL(mock_foo, func(0)) // 0を引数としてfuncが呼び出されること .Times(1) // 1度だけ呼ばれること .WillOnce(Return(true)); // funcはtrueを返す EXPECT_EQ(bar(&mock_foo), true); } test/foo_mock.cpp モッククラスの作成 モックオブジェクトを設定 テスト テストの書き方は gmockの作法に従う
  48. Copyright © Fixstars Group 実行例(1/2) 48 $ colcon test --packages-select

    example_ament_cmake_gmock … $ colcon test-result --verbose build/example_ament_cmake_gmock/Testing/20221113-1555/Test.xml: 1 test, 0 errors, 1 failure, 0 skipped - my_ament_cmake_gmock <<< failure message -- run_test.py: invoking following command in '/workspaces/test-methodlogy/build/example_ament_cmake_gmock': - /workspaces/test-methodlogy/build/example_ament_cmake_gmock/my_ament_cmake_gmock --gtest_output=xml:/workspaces/test- methodlogy/build/example_ament_cmake_gmock/test_results/example_ament_cmake_gmock/my_ament_cmake_gmock.gtest.xml Running main() from gmock_main.cc [==========] Running 2 tests from 1 test suite. [----------] Global test environment set-up. [----------] 2 tests from bar [ RUN ] bar.success [ OK ] bar.success (0 ms) [ RUN ] bar.failure /workspaces/test-methodlogy/src/one-by-one/05_example_ament_cmake_gmock/test/foo_mock.cpp:23: Failure Actual function call count doesn't match EXPECT_CALL(mock_foo, func(0))... Expected: to be called twice Actual: called once - unsatisfied and active [ FAILED ] bar.failure (0 ms) [----------] 2 tests from bar (0 ms total) 失敗するケースも追加 EXPECT_CALL(mock_foo, func(0)) .Times(2) // 2回呼ばれること .WillRepeatedly(Return(true));
  49. Copyright © Fixstars Group 実行例(2/2) 49 [----------] Global test environment

    tear-down [==========] 2 tests from 1 test suite ran. (0 ms total) [ PASSED ] 1 test. [ FAILED ] 1 test, listed below: [ FAILED ] bar.failure 1 FAILED TEST -- run_test.py: return code 1 -- run_test.py: inject classname prefix into gtest result file '/workspaces/test- methodlogy/build/example_ament_cmake_gmock/test_results/example_ament_cmake_gmock/my_ament_cmake_gmock.gtest.xml' -- run_test.py: verify result file '/workspaces/test- methodlogy/build/example_ament_cmake_gmock/test_results/example_ament_cmake_gmock/my_ament_cmake_gmock.gtest.xml' >>> build/example_ament_cmake_gmock/test_results/example_ament_cmake_gmock/my_ament_cmake_gmock.gtest.xml: 2 tests, 0 errors, 1 failure, 0 skipped - example_ament_cmake_gmock.bar failure <<< failure message /workspaces/test-methodlogy/src/one-by-one/05_example_ament_cmake_gmock/test/foo_mock.cpp:23 Actual function call count doesn't match EXPECT_CALL(mock_foo, func(0))... Expected: to be called twice Actual: called once - unsatisfied and active >>> Summary: 3 tests, 0 errors, 2 failures, 0 skipped
  50. Copyright © Fixstars Group ament_cmakeでのテスト • add_test • ament_cmake_test •

    ament_cmake_gtest • ament_cmake_gmock • ament_cmake_pytest 50
  51. Copyright © Fixstars Group 概要 ⚫ pytest を使うための ament_cmake 拡張

    ◦ https://github.com/ament/ament_cmake/tree/rolling/ament_cmake_pytest ⚫ pytest は python のテストフレームワーク ◦ https://docs.pytest.org/en/stable/ ⚫ ament_cmake は基本的に C/C++ を扱うが、そこに pytest によるテストを混 ぜたい時に使う 51
  52. Copyright © Fixstars Group CMake記述例 52 find_package(ament_cmake_pytest) ament_add_pytest_test(example_pytest test )

    . ├─ package.xml ├─ CMakeLists.txt ├─ test │ └─ test_add.py ... ファイルかディレクトリを指定 python3 -u -m pytest ${CMAKE_CURRENT_SOURCE_DIR}/test … が実行される ↓ディレクトリ構成
  53. Copyright © Fixstars Group テストの記述例 53 import pytest from example_ament_cmake_pytest.add

    import add def test_success(): # 成功するテスト assert add(1, 2) == 3 def test_failure(): # 失敗するテスト assert add(1, 2) == 4 test/test_add.py def add(a, b): return a + b example_ament_cmake_pytest/add.py
  54. Copyright © Fixstars Group 実行例(1/2) ⚫ 実行例 54 $ colcon

    test --packages-select example_ament_cmake_pytest … $ colcon test-result --verbose build/example_ament_cmake_pytest/Testing/20221113-1623/Test.xml: 1 test, 0 errors, 1 failure, 0 skipped - example_pytest <<< failure message -- run_test.py: invoking following command in '/workspaces/test-methodlogy/build/example_ament_cmake_pytest': - /usr/bin/python3.10 -u -m pytest /workspaces/test-methodlogy/src/one-by-one/09_example_ament_cmake_pytest/test -o cache_dir=/workspaces/test-methodlogy/build/example_ament_cmake_pytest/ament_cmake_pytest/example_pytest/.cache --junit- xml=/workspaces/test-methodlogy/build/example_ament_cmake_pytest/test_results/example_ament_cmake_pytest/example_pytest.xunit.xml --junit- prefix=example_ament_cmake_pytest ============================= test session starts ============================== platform linux -- Python 3.10.6, pytest-6.2.5, py-1.10.0, pluggy-0.13.0 cachedir: build/example_ament_cmake_pytest/ament_cmake_pytest/example_pytest/.cache rootdir: /workspaces/test-methodlogy plugins: ament-pep257-0.12.4, ament-xmllint-0.12.4, launch-testing-ros-0.19.3, ament-copyright-0.12.4, ament-flake8-0.12.4, ament-lint-0.12.4, launch-pytest-1.0.3, launch-testing-1.0.3, colcon-core-0.10.0 collected 2 items ../../src/one-by-one/09_example_ament_cmake_pytest/test/test_add.py .F [100%] =================================== FAILURES =================================== _________________________________ test_failure _________________________________
  55. Copyright © Fixstars Group 実行例(2/2) ⚫ 実行例 55 def test_failure():

    # 失敗するテスト > assert add(1, 2) == 4 E assert 3 == 4 E + where 3 = add(1, 2) ../../src/one-by-one/09_example_ament_cmake_pytest/test/test_add.py:10: AssertionError - generated xml file: /workspaces/test- methodlogy/build/example_ament_cmake_pytest/test_results/example_ament_cmake_pytest/example_pytest.xunit.xml - =========================== short test summary info ============================ FAILED ../../src/one-by-one/09_example_ament_cmake_pytest/test/test_add.py::test_failure ========================= 1 failed, 1 passed in 0.05s ========================== -- run_test.py: return code 1 -- run_test.py: verify result file '/workspaces/test- methodlogy/build/example_ament_cmake_pytest/test_results/example_ament_cmake_pytest/example_pytest.xunit.xml' >>> build/example_ament_cmake_pytest/test_results/example_ament_cmake_pytest/example_pytest.xunit.xml: 2 tests, 0 errors, 1 failure, 0 skipped - example_ament_cmake_pytest.src.one-by-one.09_example_ament_cmake_pytest.test.test_add test_failure <<< failure message assert 3 == 4 + where 3 = add(1, 2) >>> Summary: 3 tests, 0 errors, 2 failures, 0 skipped
  56. Copyright © Fixstars Group ROS2で可能なテスト/lint ⚫ 公式で提供されているもの 56 ament_cmake ament_python

    ⚫ https://github.com/ament/ament_cmake ⚫ ament_cmakeの拡張としてテストルー ルが存在 ⚫ 基本的に python のテストの仕組みに則っ ている ⚫ pytest と unittest が使用可能 共通 ⚫ https://github.com/ros2/launch ⚫ launch システムを使用したテストフレームワークが存在 ⚫ https://github.com/ament/ament_lint ⚫ lint のための python ツールと ament_cmake 拡張が存在
  57. Copyright © Fixstars Group ament_pythonでのテスト • ament_pythonでのテスト • pytest •

    unittest 57
  58. Copyright © Fixstars Group ament_pythonでのテスト ⚫ 基本的に python のテストフレームワークに則る ◦

    pytest か unittest ◦ pytest の場合は python3 –m pytest ... が呼ばれる ◦ unittest の場合は python3 –m unittest ... が呼ばれる ⚫ colcon test を実行した時、pytest と unittest のどちらが使用されるか? ◦ setup.py の tests_require に pytest が存在していれば pytest ◦ そうでなければ unittest ◦ あるいは、 colcon test の --python-testing オプションで指定可能 58 # colcon test –help より Arguments for 'python' packages: --python-testing {pytest,setuppy_test} The Python testing framework to use (default: determined based on the packages `tests_require`) * pytest: Use `pytest` to test Python packages * setuppy_test: Use `unittest` to test packages
  59. Copyright © Fixstars Group ament_pythonでのテスト • ament_pythonでのテスト • pytest •

    unittest 59
  60. Copyright © Fixstars Group 概要 ⚫ pytest は python 向けのテストフレームワーク

    ◦ https://docs.pytest.org/en/stable/ ◦ 標準ライブラリではないが現在主流な模様 ⚫ 必要な準備 60 from setuptools import setup package_name = 'example_ament_python_pytest' setup( name=package_name, ... tests_require=['pytest'], ... ) setup.py tests_require に pytest を追加
  61. Copyright © Fixstars Group テストの記述例 ⚫ テスト対象の関数 ⚫ テスト 61

    def add(a, b): return a + b example_ament_python_pytest/add.py import pytest from example_ament_python_pytest.add import add def test_success(): # 成功するテスト assert add(1, 2) == 3 def test_failure(): # 失敗するテスト assert add(1, 2) == 4 test/test_add.py
  62. Copyright © Fixstars Group 実行例 62 $ colcon test --packages-select

    example_ament_python_pytest … $ colcon test-result --verbose build/example_ament_python_pytest/pytest.xml: 2 tests, 0 errors, 1 failure, 0 skipped - example_ament_python_pytest.test.test_add test_failure <<< failure message assert 3 == 4 + where 3 = add(1, 2) >>> Summary: 2 tests, 0 errors, 1 failure, 0 skipped
  63. Copyright © Fixstars Group ament_pythonでのテスト • ament_pythonでのテスト • pytest •

    unittest 63
  64. Copyright © Fixstars Group ament_python - unittest ⚫ python の標準ライブラリに含まれているテストフレームワーク

    ◦ https://docs.python.org/ja/3/library/unittest.html ⚫ 必要な準備 ◦ 特になし ◦ tests_require に pytest を追加しないこと ⚫ 注意点 ◦ XML形式のレポートが生成されず、 colcon test-result でテスト結果を得ることができない ◦ colcon test 時に標準エラーに unittest のメッセージが出力される ◦ 失敗時は colcon test の終了コードが非ゼロになる 64
  65. Copyright © Fixstars Group ament_python - unittest 65 import unittest

    from example_ament_python_unittest.add import add class AddTestCase(unittest.TestCase): def test_success(self): self.assertEqual(add(1, 2), 3) def test_failure(self): self.assertEqual(add(1, 2), 4) test/test_add.py
  66. Copyright © Fixstars Group 実行例 66 $ colcon test --packages-select

    example_ament_python_unittest Starting >>> example_ament_python_unittest --- stderr: example_ament_python_unittest test_failure (test.test_add.AddTestCase) ... FAIL test_success (test.test_add.AddTestCase) ... ok ====================================================================== FAIL: test_failure (test.test_add.AddTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/workspaces/test-methodlogy/src/one-by-one/08_example_ament_python_unittest/test/test_add.py", line 9, in test_failure self.assertEqual(add(1, 2), 4) AssertionError: 3 != 4 ---------------------------------------------------------------------- Ran 2 tests in 0.000s FAILED (failures=1) --- Failed <<< example_ament_python_unittest [0.30s, exited with code 1] Summary: 0 packages finished [0.53s] 1 package failed: example_ament_python_unittest 1 package had stderr output: example_ament_python_unittest
  67. Copyright © Fixstars Group ROS2で可能なテスト/lint ⚫ 公式で提供されているもの 67 ament_cmake ament_python

    ⚫ https://github.com/ament/ament_cmake ⚫ ament_cmakeの拡張としてテストルー ルが存在 ⚫ 基本的に python のテストの仕組みに則っ ている ⚫ pytest と unittest が使用可能 共通 ⚫ https://github.com/ros2/launch ⚫ launch システムを使用したテストフレームワークが存在 ⚫ https://github.com/ament/ament_lint ⚫ lint のための python ツールと ament_cmake 拡張が存在
  68. Copyright © Fixstars Group launch integration test • launch integration

    test とは • launch_testing • launch_pytest 68
  69. Copyright © Fixstars Group launch integration test とは ⚫ launch

    システムを使ったテストを作成できるフレームワーク ⚫ 機能(リポジトリ説明より※) ◦ テストで実行する全てのプロセスの終了コードの取得 ◦ プロセスが正常終了したかの確認、あるいは特定の終了コードで終わったかの確認 ◦ プロセスが意図せず死んだ時にテストを失敗させることができる ◦ 全てのプロセスの標準出力/標準エラーを取得 ◦ 任意のコマンドラインをテストに使用できる ◦ テスト自体はプロセスの起動と並行に実行でき、実行中のプロセスとやりとりできる ⚫ Python で作られている ⚫ launch_testing と launch_pytest がある 69 ※ https://github.com/ros2/launch/tree/rolling/launch_testing
  70. Copyright © Fixstars Group launch integration test とは ⚫ 処理の概要(例)

    70 Test System PUT launch publish subscribe test shutdown process under test ROS2のノードなど
  71. Copyright © Fixstars Group launch integration test • launch integration

    test とは • launch_testing • launch_pytest 71
  72. Copyright © Fixstars Group 概要 ⚫ unittest ベースとした launch integration

    test のフレームワーク ◦ https://github.com/ros2/launch/tree/rolling/launch_testing ⚫ 例題 ◦ Int32 の値をサブスクライブし、2倍にしてパブリッシュする twice ノードをテストする ◦ やりたいのは、 ▪ twice ノードを起動する ▪ テスト用のノードを作り、 Int32 の値を送る ▪ twice ノードから Int32 の値を受け取り、それが送った値の2倍になっているか検証する 72
  73. Copyright © Fixstars Group テストの記述例(launch部分) 73 @pytest.mark.launch_test def generate_test_description(): return

    launch.LaunchDescription([ launch_ros.actions.Node( package="example_launch_testing_ament_cmake", executable="twice", ), launch_testing.actions.ReadyToTest(), ]) generate_test_description という関数を定義する。 記述方法は ROS2 の launch(python版)と同じ。
  74. Copyright © Fixstars Group テストの記述例(launch部分) 74 @pytest.mark.launch_test def generate_test_description(): return

    launch.LaunchDescription([ launch_ros.actions.Node( package="example_launch_testing_ament_cmake", executable="twice", ), launch_testing.actions.ReadyToTest(), ]) twice ノードを起動 するアクション テスト用のアクション
  75. Copyright © Fixstars Group テストの記述例(テスト用ノード(1/2)) 75 class DummyTestNode(Node): def __init__(self):

    super().__init__("test_node") self._msgs = [] self._pub = self.create_publisher(Int32, "src", 10) self._sub = self.create_subscription( Int32, "dst", lambda msg: self._msgs.append(msg), 10 ) assert self._wait_for_connect() def _wait_for_connect(self, timeout_s=5): end_time = time.time() + timeout_s while time.time() < end_time: cnt = self._pub.get_subscription_count() if cnt > 0: return True time.sleep(0.1) return False ... /src に送って、 /dst から受け取る 接続されている subscription の数を数えることで、接続が 確立されたことを確認する
  76. Copyright © Fixstars Group テストの記述例(テスト用ノード(2/2)) 76 class DummyTestNode(Node): ... def

    publish(self, data): self._pub.publish(data) def get_message(self, timeout_s=5.0): start_len = len(self._msgs) executor = rclpy.executors.SingleThreadedExecutor() executor.add_node(self) try: end_time = time.time() + timeout_s while time.time() < end_time: executor.spin_once(timeout_sec=0.1) if start_len != len(self._msgs): break finally: executor.remove_node(self) executor.shutdown() assert start_len != len(self._msgs) return self._msgs[-1] Executor の spin_onceを使い、 受信データをポーリング
  77. Copyright © Fixstars Group テストの記述例(unittest部分) 77 class TestTwice(unittest.TestCase): @classmethod def

    setUpClass(cls): rclpy.init() @classmethod def tearDownClass(cls): rclpy.shutdown() def setUp(self): self._node = DummyTestNode() def tearDown(self): self._node.destroy_node() def test_success(self): test_data = Int32(data=1) self._node.publish(test_data) ans = self._node.get_message() self.assertEqual(ans, Int32(data=2)) def test_success2(self): ... setUpClass setUp test_xxx tearDown tearDownClass launch PUT shutdown PUT each test cases
  78. Copyright © Fixstars Group テストの記述例(unittest部分) 78 class TestTwice(unittest.TestCase): @classmethod def

    setUpClass(cls): rclpy.init() @classmethod def tearDownClass(cls): rclpy.shutdown() def setUp(self): self._node = DummyTestNode() def tearDown(self): self._node.destroy_node() def test_success(self): test_data = Int32(data=1) self._node.publish(test_data) ans = self._node.get_message() self.assertEqual(ans, Int32(data=2)) def test_success2(self): ... setUpClass setUp test_xxx tearDown tearDownClass launch PUT shutdown PUT each test cases
  79. Copyright © Fixstars Group 実行例 79 $ launch_test test/test_twice_launch.py [INFO]

    [launch]: All log files can be found below /home/vscode/.ros/log/2022-11-14-15-24-26-445770-62e34440bc2c-2166924 [INFO] [launch]: Default logging verbosity is set to INFO test_failure (test_twice_launch.TestTwice) ... [INFO] [twice-1]: process started with pid [2166932] FAIL test_success (test_twice_launch.TestTwice) ... ok test_success2 (test_twice_launch.TestTwice) ... ok ====================================================================== FAIL: test_failure (test_twice_launch.TestTwice) ---------------------------------------------------------------------- Traceback (most recent call last): File "/workspaces/test-methodlogy/src/one-by- one/10_example_launch_testing_ament_cmake/test/test_twice_launch.py", line 77, in test_failure self.assertEqual(ans, Int32(data=3)) AssertionError: std_msgs.msg.Int32(data=2) != std_msgs.msg.Int32(data=3) ---------------------------------------------------------------------- Ran 3 tests in 0.471s FAILED (failures=1) [INFO] [twice-1]: sending signal 'SIGINT' to process[twice-1] [twice-1] [INFO] [1668439466.941480504] [rclcpp]: signal_handler(signum=2) [INFO] [twice-1]: process has finished cleanly [pid 2166932] ---------------------------------------------------------------------- Ran 0 tests in 0.000s OK コマンドラインツールの launch_test を使用
  80. Copyright © Fixstars Group ament_cmakeから使う場合 ⚫ launch_testing_ament_cmake を利用する ⚫ CMake記述例

    80 if(BUILD_TESTING) find_package(launch_testing_ament_cmake REQUIRED) add_launch_test(test/test_twice_launch.py) endif() ctest 経由で launch_test が実行される。 launch_test には XML形式のレポート作成機能があるため、それがテストの結果として得られる。
  81. Copyright © Fixstars Group ament_pythonから使う場合 ⚫ package.xml の test_depend に

    launch_testing を追加する ⚫ generate_test_description に @pytest.mark.launch_test をデコレータとして 付ける ⚫ ファイル名を test_xxx.py か xxx_test.py (pytest のルール)にする 81
  82. Copyright © Fixstars Group launch integration test • launch integration

    test とは • launch_testing • launch_pytest 82
  83. Copyright © Fixstars Group 概要 ⚫ pytest ベースとした launch integration

    test のフレームワーク ◦ humble で導入された ◦ https://github.com/ros2/launch/tree/rolling/launch_pytest ⚫ launch_testing との違いは何か ◦ 純粋な pytest 拡張である ▪ テストケースを名前でフィルターして実行できる ▪ 失敗し得るテストケースというマークを付けられる ▪ pytest が提供するエラーレポートの仕組みを使える ◦ launch integration test を実現するという目的に違いはない 83
  84. Copyright © Fixstars Group テストの記述例(launch部分) 84 @launch_pytest.fixture def generate_test_description(): return

    launch.LaunchDescription( [ launch_ros.actions.Node( package="example_launch_pytest", executable="twice", ), ] ) launch_testing と同じく launch を 記述する
  85. Copyright © Fixstars Group テストの記述例(launch部分) 85 @launch_pytest.fixture def generate_test_description(): return

    launch.LaunchDescription( [ launch_ros.actions.Node( package="example_launch_pytest", executable="twice", ), ] ) @launch_pytest.fixture デコレータ をつける 関数名は何でも良い(明示的に指定するため) launch_testing.actions.ReadyToTest() は省略可。 なければ自動的に追加される。
  86. Copyright © Fixstars Group テストの記述例(テスト用ノード) 86 class DummyTestNode(Node): def __init__(self):

    super().__init__("test_node") self._pub = self.create_publisher(Int32, "src", 10) self._sub = self.create_subscription(Int32, "dst", self._msg_received, 10) self.msgs = [] self.msg_event_object = Event() def start(self): self._ros_spin_thread = Thread( target=lambda node: rclpy.spin(node), args=(self,) ) self._ros_spin_thread.start() assert self._wait_for_connect() def publish(self, data): self._pub.publish(data) def _msg_received(self, msg): self.msgs.append(msg) self.msg_event_object.set() launch_testing の時と凡そ同じだが、 rclpy.spin を別スレッドで実行するよ うにしている。 また、 Event を使ったスレッドコン トロールも活用
  87. Copyright © Fixstars Group テストの記述例(pytest部分) 87 set up test_xxx tear

    down launch PUT shutdown PUT each test cases @pytest.fixture def make_test_node(): rclpy.init() node = DummyTestNode() node.start() yield node rclpy.shutdown() @pytest.mark.launch(fixture=generate_test_description) def test_success(make_test_node): node = make_test_node node.publish(Int32(data=2)) assert node.msg_event_object.wait(timeout=5.0), "Did not receive msgs" assert node.msgs[0] == Int32(data=4) テストノードを作るフィクスチャ テストケース
  88. Copyright © Fixstars Group テストの記述例(pytest部分) ⚫ 実行順 88 set up

    test_xxx tear down launch PUT shutdown PUT each test cases @pytest.fixture def make_test_node(): rclpy.init() node = DummyTestNode() node.start() yield node rclpy.shutdown() @pytest.mark.launch(fixture=generate_test_description) def test_success(make_test_node): node = make_test_node node.publish(Int32(data=2)) assert node.msg_event_object.wait(timeout=5.0), "Did not receive msgs" assert node.msgs[0] == Int32(data=4)
  89. Copyright © Fixstars Group テストの記述例(pytest部分) ⚫ フィクスチャ周り 89 set up

    test_xxx tear down launch PUT shutdown PUT each test cases @pytest.fixture def make_test_node(): rclpy.init() node = DummyTestNode() node.start() yield node rclpy.shutdown() @pytest.mark.launch(fixture=generate_test_description) def test_success(make_test_node): node = make_test_node node.publish(Int32(data=2)) assert node.msg_event_object.wait(timeout=5.0), "Did not receive msgs" assert node.msgs[0] == Int32(data=4) yield で返されるオブジェクトを得られる 名前指定でフィクスチャが使用される LaunchDescriptionを返すフィクスチャを指定
  90. Copyright © Fixstars Group 実行例 90 $ python3 -m pytest

    test -q --show-capture no ..F [100%] ============================================ FAILURES ============================================= __________________________________________ test_failure ___________________________________________ make_test_node = <test_twice_launch.DummyTestNode object at 0x7f8f5b844d60> @pytest.mark.launch(fixture=generate_test_description) def test_failure(make_test_node): node = make_test_node node.publish(Int32(data=3)) assert node.msg_event_object.wait(timeout=5.0), "Did not receive msgs" > assert node.msgs[0] == Int32(data=5) E assert std_msgs.msg.Int32(data=6) == std_msgs.msg.Int32(data=5) E + where std_msgs.msg.Int32(data=5) = Int32(data=5) test/test_twice_launch.py:48: AssertionError ===================================== short test summary info ===================================== FAILED test/test_twice_launch.py::test_failure - assert std_msgs.msg.Int32(data=6) == std_msgs.m... 1 failed, 2 passed in 1.37s pytest で実行可能
  91. Copyright © Fixstars Group 使用方法 ⚫ ament_cmake から使う場合 ◦ pytest

    拡張であるため ament_cmake_pytest を併用する ⚫ ament_pythonから使う場合 ◦ package.xml の test_depend に launch_pytest を追加する 91 find_package(ament_cmake_pytest) ament_add_pytest_test(pytest test ) CMakeLists.txt <test_depend>ament_cmake_pytest</test_depend> <test_depend>launch_pytest</test_depend> package.xml
  92. Copyright © Fixstars Group ROS2で可能なテスト/lint ⚫ 公式で提供されているもの 92 ament_cmake ament_python

    ⚫ https://github.com/ament/ament_cmake ⚫ ament_cmakeの拡張としてテストルー ルが存在 ⚫ 基本的に python のテストの仕組みに則っ ている ⚫ pytest と unittest が使用可能 共通 ⚫ https://github.com/ros2/launch ⚫ launch システムを使用したテストフレームワークが存在 ⚫ https://github.com/ament/ament_lint ⚫ lint のための python ツールと ament_cmake 拡張が存在
  93. Copyright © Fixstars Group lint • ROS2でのlint • ament_cmake での使い方

    • ament_python での使い方 93
  94. Copyright © Fixstars Group lintとは ⚫ 静的解析ツールを意味する ◦ コードフォーマットの確認 ◦

    バグの原因になりそうな部分の検知(未使用変数であったり) ◦ etc. ⚫ ROS2では以下の形で提供されている ◦ Pythonライブラリ ◦ コマンドラインツール ◦ ament_cmake 拡張 94
  95. Copyright © Fixstars Group lint一覧 名前 ターゲット 概要 ament_xmllint XML

    xmllint によるスタイルチェック ament_lint_cmake CMake CMakeLint によるスタイルチェック ament_copyright C/C++/CMake/Python copyright と license が書かれているかチェック ament_clang_format C/C++ clang-format によるスタイルチェック(ルールファイルが含まれている) ament_uncrustify C/C++ Uncrustifyによるスタイルチェック ament_clang_tidy C/C++ clang-tidy によるコードチェック 未使用変数のチェック、暗黙的なキャストのチェックなど。(項目は沢山ある) ament_cppcheck C/C++ CppCheckによる静的解析 メモリリークの可能性などを指摘できる ament_cpplint C/C++ cpplintによるスタイルチェック Google C++コーディングスタイルに準じているかチェックする ament_pclint C/C++ PCLintによる静的解析 MISRAなどが含まれる。有料 ament_flake8 Python flake8によるシンタックスとスタイルチェック ament_mypy Python mypyによるシンタックスとスタイルチェック ament_pep257 Python pep257による docstring スタイルチェック ament_pycodestyle Python pycodesstyleによるスタイルチェック ament_pyflakes Python Pyflakesによるスタイルチェック 95 共通 C/C++向け Python向け
  96. Copyright © Fixstars Group 共通 ⚫ これらは必要に応じて入れれば良く、lint同士が競合するということはない 96 名前 ターゲット

    概要 ament_xmllint XML xmllint によるスタイルチェック。 XMLスキーマの妥当性を確認する。 ROS2では http://download.ros.org/schema/package_format3.xsd を使用 ament_lint_cmake CMake CMakeLint によるスタイルチェック。 詳細は https://github.com/cmake-lint/cmake-lint わかりやすいところでは余計な空白がないか、など ament_copyright C/C++/CMake/Python copyright と license が書かれているかチェック。 c, .cc, .cpp, .cxx, .h, .hh, .hpp, .hxx, .cmake, .py のファイルが検証される。 ライセンスの表記方法にテンプレート(※)があり、それに従っているかどうか検証される。 ※ https://github.com/ament/ament_lint/tree/rolling/ament_copyright/ament_copyright/template
  97. Copyright © Fixstars Group C/C++ ⚫ コードフォーマット。排他的でどちらか1つ使用する ⚫ その他静的解析。排他的ではなく、それぞれ検証項目が違うと考えれば良い 名前

    概要 ament_clang_tidy clang-tidy によるコードチェック。 未使用変数のチェック、暗黙的なキャストのチェックなど。(項目は沢山ある) ament_cppcheck CppCheckによる静的解析 メモリリークの可能性などを指摘できる ament_cpplint cpplintによるスタイルチェック Google C++コーディングスタイルに準じているかチェックする ament_pclint PC-Lintによる静的解析 MISRAなどが含まれる。有料 97 名前 概要 ament_clang_format コードがフォーマット済みであるかを検証。clang-format を使用 ament_uncrustify コードがフォーマット済みであるかを検証。Uncrustify を使用
  98. Copyright © Fixstars Group Python ⚫ コードフォーマット ◦ → 存在しない

    ⚫ その他静的解析 ◦ Pythonにはコードの体裁に関する規約があり、それらがPEP8やPEP257となっている ◦ 必要に応じて選択すれば良いが、総括すると flake8 と mypy、 pep257 を入れると被りがない 名前 ターゲット 概要 ament_flake8 論理エラー/PEP8/循環参照 PyFlakesとpycodestyleとmccabeのラッパー ament_mypy Type Hints (PEP484) 型ヒントのチェック ament_pep257 Docstring (PEP257) Docstring のチェック ament_pycodestyle Style Guide for Python Code(PEP8) コーディングスタイルのチェック ament_pyflakes 論理エラー 論理的なエラー(未使用ライブラリや未定義のシンボルなど)を検出 98
  99. Copyright © Fixstars Group 結局何を使えば良いか? ⚫ 必要に応じて選択する ◦ ros2 pkg

    create ... では以下がデフォルトになっている ament_cmake ⚫ ament_lint_common ⚫ ROS2公式で推奨されている※ ⚫ 以下のパッケージの組み合わせ ⚫ ament_cmake_copyright ⚫ ament_cmake_cppcheck ⚫ ament_cmake_cpplint ⚫ ament_cmake_flake8 ⚫ ament_cmake_lint_cmake ⚫ ament_cmake_pep257 ⚫ ament_cmake_uncrustify ⚫ ament_cmake_xmllint ⚫ ros2 pkg create 直後では copyright と cpplint は無効化 ※ https://docs.ros.org/en/rolling/How-To-Guides/Ament-CMake-Documentation.html#linting ament_python ⚫ ament_copyright ⚫ ros2 pkg create 直後では無効化 ⚫ ament_flake8 ⚫ ament_pep257 99
  100. Copyright © Fixstars Group コマンドラインでの使用例 100 $ ament_uncrustify No code

    style divergence in file 'include/example_ament_cmake_lint/add.hpp' Code style divergence in file 'src/add.cpp': --- src/add.cpp +++ src/add.cpp.uncrustify @@ -8 +8 @@ -int add(int a, int b) {return a + b;} +int add(int a, int b) {return a + b;} 1 files with code style divergence $ ament_uncrustify --reformat No code style divergence in file 'include/example_ament_cmake_lint/add.hpp' Code style divergence in file 'src/add.cpp': reformatted file 1 files with code style divergence 解析だけでなくコード修正可能 なものもある
  101. Copyright © Fixstars Group lint • ROS2でのlint • ament_cmake での使い方

    • ament_python での使い方 101
  102. Copyright © Fixstars Group ament_cmake での使い方 ⚫ 基本的にament_lint_auto を利用すれば良い ⚫

    ament_lint_auto のマクロは package.xml を読み取る ◦ test_depend で指定された lint は自動的にロードされ、lintが実行されるようになる 102 <test_depend>ament_lint_auto</test_depend> <test_depend>ament_cmake_copyright</test_depend> <test_depend>ament_cmake_uncrustify</test_depend> package.xml find_package(ament_lint_auto REQUIRED) ament_lint_auto_find_test_dependencies() CMakeLists.txt
  103. Copyright © Fixstars Group 応用 ⚫ 特定の lint を ament_lint_auto

    での自動実行から外したい場合 ◦ AMENT_LINT_AUTO_EXCLUDE を使用する 103 set(AMENT_LINT_AUTO_EXCLUDE ament_cmake_uncrustify) ament_lint_auto_find_test_dependencies() ament_uncrustify( CONFIG_FILE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/resource/ament_code_style.cfg> ) CMakeLists.txt ament_cmake_uncrustify を除外 設定を変えて lint を実行 指定可能なオプションは CMakeマクロのドキュメントコメントを見る。↓は ament_uncrustifyの場合。 https://github.com/ament/ament_lint/blob/rolling/ament_cmake_uncrustify/cmake/ament_uncrustify.cmake
  104. Copyright © Fixstars Group 実行例(1/2) 104 $ colcon test --packages-select

    example_ament_cmake_lint ... $ colcon test-result --verbose build/example_ament_cmake_lint/Testing/20221115-1302/Test.xml: 2 tests, 0 errors, 1 failure, 0 skipped - uncrustify <<< failure message -- run_test.py: invoking following command in '/workspaces/test-methodlogy/src/lint/example_ament_cmake_lint': - /opt/ros/humble/bin/ament_uncrustify --xunit-file /workspaces/test- methodlogy/build/example_ament_cmake_lint/test_results/example_ament_cmake_lint/uncrustify.xunit.xml Code style divergence in file 'src/add.cpp': --- src/add.cpp +++ src/add.cpp.uncrustify @@ -8 +8 @@ -int add(int a, int b) {return a + b;} +int add(int a, int b) {return a + b;} 1 files with code style divergence No code style divergence in file 'include/example_ament_cmake_lint/add.hpp' -- run_test.py: return code 1 -- run_test.py: verify result file '/workspaces/test- methodlogy/build/example_ament_cmake_lint/test_results/example_ament_cmake_lint/uncrustify.xunit.xml'
  105. Copyright © Fixstars Group 実行例(2/2) 105 >>> build/example_ament_cmake_lint/test_results/example_ament_cmake_lint/uncrustify.xunit.xml: 2 tests,

    0 errors, 1 failure, 0 skipped - example_ament_cmake_lint.uncrustify src/add.cpp <<< failure message Diff with 5 lines >>> Summary: 6 tests, 0 errors, 2 failures, 0 skipped
  106. Copyright © Fixstars Group lint • ROS2でのlint • ament_cmake での使い方

    • ament_python での使い方 106
  107. Copyright © Fixstars Group ament_pythonでの使い方 ⚫ 基本的に pytest で実装する ◦

    lintパッケージはPythonライブラリとして使用できる ▪ main関数を利用 107 <test_depend>ament_xmllint</test_depend> <test_depend>ament_flake8</test_depend> <test_depend>python3-pytest</test_depend> package.xml
  108. Copyright © Fixstars Group xmllintのテスト 108 from ament_xmllint.main import main

    import pytest @pytest.mark.xmllint @pytest.mark.linter def test_xmllint(): rc = main(argv=['.']) assert rc == 0, 'Found errors' test/test_fxmllint.py
  109. Copyright © Fixstars Group flake8のテスト 109 from ament_flake8.main import main_with_errors

    import pytest @pytest.mark.flake8 @pytest.mark.linter def test_flake8(): rc, errors = main_with_errors(argv=[]) assert rc == 0, ¥ 'Found %d code style errors / warnings:¥n' % len(errors) + ¥ '¥n'.join(errors) test/test_flake8.py flake8だけ main_with_errors があるの でそちらを使用 (foxy以降)
  110. Copyright © Fixstars Group 実行例 110 $ colcon test --packages-select

    example_ament_python_lint ... $ colcon test-result --verbose build/example_ament_python_lint/pytest.xml: 2 tests, 0 errors, 1 failure, 0 skipped - example_ament_python_lint.test.test_flake8 test_flake8 <<< failure message AssertionError: Found 1 code style errors / warnings: ./example_ament_python_lint/__init__.py:1:1: F401 'os' imported but unused assert 1 == 0 >>> Summary: 2 tests, 0 errors, 1 failure, 0 skipped
  111. Copyright © Fixstars Group サンプルアプリでのテ スト実装

  112. Copyright © Fixstars Group サンプルアプリでのテスト 実装 • 概要 • cpp_calc

    • py_accum • integration 112
  113. Copyright © Fixstars Group サンプルアプリでのテスト実装 ⚫ C++, Python それぞれで簡単なノードを作成しそれらを組み合わせたシステ ムを作る。

    ◦ C++ → cpp_calc パッケージ ◦ Python → py_accum パッケージ ◦ 組み合わせる → integration パッケージ ⚫ 各パッケージについて以下の順番で説明 ◦ パッケージ概要 ◦ パッケージ作成 ◦ lintの設定 ◦ ロジック実装及び単体テスト ◦ ノード実装及び launch integration test 113 cpp_calc/twice node py_accum/accum node integration
  114. Copyright © Fixstars Group サンプルアプリでのテスト 実装 • 概要 • cpp_calc

    • py_accum • integration 114
  115. Copyright © Fixstars Group 概要 ⚫ 入力値を2倍にして出力するtwiceノードを実装 ◦ ament_cmake/c++を使用 ⚫

    最終的なフォルダ構成 115 . ├── package.xml ├── CMakeLists.txt ├── include │ └── cpp_calc │ ├── twice.hpp │ └── twice_node.hpp ├── src │ ├── main.cpp │ ├── twice.cpp │ └── twice_node.cpp └── test ├── launch │ └── twice_node_test.py └── unittest └── twice_test.cpp cpp_calc/twice node py_accum/accum node integration
  116. Copyright © Fixstars Group パッケージ作成 116 <?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>cpp_calc</name> <version>0.0.0</version> <description>TODO: Package description</description> <maintainer email="[email protected]">Shuhei Aoki</maintainer> <license>MIT</license> <buildtool_depend>ament_cmake_auto</buildtool_depend> <depend>rclcpp</depend> <depend>std_msgs</depend> <test_depend>ament_lint_auto</test_depend> <test_depend>ament_lint_common</test_depend> <test_depend>ament_cmake_gtest</test_depend> <test_depend>launch_testing_ament_cmake</test_depend> <export> <build_type>ament_cmake</build_type> </export> </package> gtest と launch_testing を追加 ライセンス設定 簡易化のためにament_cmake_auto を使用 ros2 pkg create --build-type ament_cmake cpp_calc package.xml
  117. Copyright © Fixstars Group lintの設定 ⚫ デフォルトでは copyright と cpplint

    は無効 ⚫ 今回は使用したいので有効化 117 if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) # the following line skips the linter which checks for copyrights # comment the line when a copyright and license is added to all source files set(ament_cmake_copyright_FOUND TRUE) # the following line skips cpplint (only works in a git repo) # comment the line when this package is in a git repo and when # a copyright and license is added to all source files set(ament_cmake_cpplint_FOUND TRUE) ament_lint_auto_find_test_dependencies() endif() 削除 CMakeLists.txt
  118. Copyright © Fixstars Group ロジック実装 ⚫ ロジックとROS2のノード実装と分離することでロジックの単体テストを 可能とする 118 //

    Copyright (c) 2022 Fixstars inc. // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. #pragma once #include <cstdint> namespace cpp_calc { int32_t do_twice(int32_t v); } // namespace cpp_calc // Copyright (c) 2022 Fixstars inc. // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. #include "cpp_calc/twice.hpp" namespace cpp_calc { int32_t do_twice(int32_t v) { return v * 2; } } // namespace cpp_calc include/cpp_calc/twice.hpp src/twice.cpp
  119. Copyright © Fixstars Group 単体テスト ⚫ この段階で do_twice の単体テストを実行可能 119

    // Copyright (c) 2022 Fixstars inc. // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. #include <cpp_calc/twice.hpp> #include <gtest/gtest.h> TEST(do_twice, two_sohuld_be_four) { ASSERT_EQ(cpp_calc::do_twice(2), 4); } find_package(ament_cmake_auto REQUIRED) ament_auto_find_build_dependencies() ament_auto_add_library(twice_lib SHARED src/twice.cpp ) if(BUILD_TESTING) ament_auto_find_test_dependencies() ament_auto_add_gtest(twice_test test/unittest/twice_test.cpp ) endif() test/unittest/twice_test.cpp CMakeLists.txt
  120. Copyright © Fixstars Group ノード実装 ⚫ do_twice を使うノードを作成 120 #pragma

    once #include <rclcpp/rclcpp.hpp> #include <std_msgs/msg/int32.hpp> namespace cpp_calc { using Int32 = std_msgs::msg::Int32; class TwiceNode : public rclcpp::Node { public: TwiceNode(); private: rclcpp::Subscription<Int32>::SharedPtr sub_; rclcpp::Publisher<Int32>::SharedPtr pub_; }; } // namespace cpp_calc #include "cpp_calc/twice_node.hpp" #include "cpp_calc/twice.hpp" namespace cpp_calc { TwiceNode::TwiceNode() : rclcpp::Node("twice", "") { pub_ = this->create_publisher<Int32>("dst", 10); sub_ = this->create_subscription<Int32>( "src", 10, [this](const Int32::ConstSharedPtr src) -> void { auto output = Int32(); output.data = do_twice(src->data); this->pub_->publish(output); }); } } // namespace cpp_calc ※コピーライトも必要だが長くなるので以降は省略 src/twice_node.cpp include/cpp_calc/twice_node.hpp
  121. Copyright © Fixstars Group ノード実装 ⚫ do_twice を使うノードを作成 121 #include

    <rclcpp/rclcpp.hpp> #include <cpp_calc/twice_node.hpp> int main(int argc, char * argv[]) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<cpp_calc::TwiceNode>()); rclcpp::shutdown(); return 0; } ament_auto_add_library(twice_lib SHARED src/twice.cpp src/twice_node.cpp ) ament_auto_add_executable(twice src/main.cpp ) CMakeLists.txt src/main.cpp
  122. Copyright © Fixstars Group launch integration test (launch_testing) ⚫ これによりノードとしての振舞いをテストできる

    122 if(BUILD_TESTING) ament_auto_find_test_dependencies() ament_auto_add_gtest(twice_test test/unittest/twice_test.cpp ) add_launch_test(test/launch/twice_node_test.py) endif() CMakeLists.txt
  123. Copyright © Fixstars Group test/launch/twice_node_test.py(1/3) 123 import unittest import time

    import launch import launch_ros.actions import launch_testing import launch_testing.actions import pytest from rclpy.node import Node import rclpy import rclpy.executors from std_msgs.msg import Int32 @pytest.mark.launch_test def generate_test_description(): return launch.LaunchDescription( [ launch_ros.actions.Node( package="cpp_calc", executable="twice", ), launch_testing.actions.ReadyToTest(), ] )
  124. Copyright © Fixstars Group test/launch/twice_node_test.py(2/3) 124 class TestTwice(unittest.TestCase): @classmethod def

    setUpClass(cls): rclpy.init() @classmethod def tearDownClass(cls): rclpy.shutdown() def setUp(self): self._node = DummyTestNode() def tearDown(self): self._node.destroy_node() def test_success(self): test_data = Int32(data=1) self._node.publish(test_data) ans = self._node.get_message() self.assertEqual(ans, Int32(data=2))
  125. Copyright © Fixstars Group test/launch/twice_node_test.py(3/3) 125 class DummyTestNode(Node): def __init__(self):

    super().__init__("test_node") self._msgs = [] self._pub = self.create_publisher(Int32, "src", 10) self._sub = self.create_subscription( Int32, "dst", lambda msg: self._msgs.append(msg), 10 ) assert self._wait_for_connect() def _wait_for_connect(self, timeout_s=5): end_time = time.time() + timeout_s while time.time() < end_time: cnt = self._pub.get_subscription_count() if cnt > 0: return True time.sleep(0.1) return False def publish(self, data): self._pub.publish(data) def get_message(self, timeout_s=5.0): start_len = len(self._msgs) executor = rclpy.executors.SingleThreadedExecutor() executor.add_node(self) try: end_time = time.time() + timeout_s while time.time() < end_time: executor.spin_once(timeout_sec=0.1) if start_len != len(self._msgs): break finally: executor.remove_node(self) executor.shutdown() assert start_len != len(self._msgs) return self._msgs[-1]
  126. Copyright © Fixstars Group サンプルアプリでのテスト 実装 • 概要 • cpp_calc

    • py_accum • integration 126
  127. Copyright © Fixstars Group 概要 ⚫ 入力値を累積し、累積値を出力する ◦ ament_python/Python を使用

    ⚫ 最終的なフォルダ構成 127 . ├── package.xml ├── setup.cfg ├── setup.py ├── py_accum │ ├── __init__.py │ ├── accumulator.py │ └── accumulator_node.py ├── resource │ └── py_accum └── test ├── launch │ └── test_accum.py ├── test_copyright.py ├── test_flake8.py ├── test_pep257.py └── unittest └── test_accumulator.py cpp_calc/twice node py_accum/accum node integration
  128. Copyright © Fixstars Group パッケージ作成 128 <?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>py_accum</name> <version>0.0.0</version> <description>TODO: Package description</description> <maintainer email="[email protected]">Shuhei Aoki</maintainer> <license>MIT</license> <depend>rclpy</depend> <depend>std_msgs</depend> <test_depend>ament_copyright</test_depend> <test_depend>ament_flake8</test_depend> <test_depend>ament_pep257</test_depend> <test_depend>python3-pytest</test_depend> <test_depend>launch_pytest</test_depend> <export> <build_type>ament_python</build_type> </export> </package> launch_pytest を追加 ライセンス設定 ros2 pkg create --build-type ament_python py_accum package.xml
  129. Copyright © Fixstars Group lintの設定 ⚫ ros2 pkg create の段階で必要なテストは作成されている

    ⚫ copyright は無効になっているので有効化する 129 from ament_copyright.main import main import pytest # Remove the `skip` decorator once the source file(s) have a copyright header @pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') @pytest.mark.copyright @pytest.mark.linter def test_copyright(): rc = main(argv=['.', 'test']) assert rc == 0, 'Found errors' 削除 test/test_copyright.py
  130. Copyright © Fixstars Group ロジック実装 ⚫ ロジックとROS2のノード実装と分離する ことでロジックの単体テストを可能とす る ⚫

    単体テスト実装 ◦ この段階で Accumulatorクラスのテストが 可能 130 class Accumulator: def __init__(self): self._data = 0 def add(self, v): self._data += v def get(self): return self._data from py_accum.accumulator import Accumulator def test_success(): acc = Accumulator() for i in range(10): acc.add(i) assert acc.get() == 45 py_accum/accumulator.py test/unittest/test_accumulator.py
  131. Copyright © Fixstars Group ノード実装(1/2) ⚫ Accumulator を使うノー ドを実装 131

    import rclpy from rclpy.node import Node from std_msgs.msg import Int32 from .accumulator import Accumulator class AccumulatorNode(Node): def __init__(self): super().__init__("accum") self._acc = Accumulator() self._pub = self.create_publisher(Int32, "dst", 10) self._sub = self.create_subscription(Int32, "src", self.callback, 10) def callback(self, msg): self._acc.add(msg.data) self._pub.publish(Int32(data=self._acc.get())) def main(args=None): rclpy.init(args=args) node = AccumulatorNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == "__main__": main() py_accum/accumulator_node.py
  132. Copyright © Fixstars Group ノード実装(2/2) ⚫ エントリーポイントに追加 132 entry_points={ "console_scripts":

    ["accum=py_accum.accumulator_node:main"], }, setup.py
  133. Copyright © Fixstars Group launch integration test (launch_pytest) ⚫ test/launch/test_accum.py

    を作成 ⚫ これによりノードとしての振舞いをテストできる 133
  134. Copyright © Fixstars Group test/launch/test_accum.py 134 import launch import launch_ros.actions

    import pytest import launch_pytest import rclpy from rclpy.node import Node from std_msgs.msg import Int32 import time from threading import Thread @launch_pytest.fixture def generate_test_description(): return launch.LaunchDescription( [ launch_ros.actions.Node( package="py_accum", executable="accum", ), ] ) @pytest.mark.launch(fixture=generate_test_description) def test_accumulation(make_test_node): node = make_test_node for i in range(10): node.publish(Int32(data=i)) time.sleep(0.01) end_time = time.time() + 5 while time.time() < end_time: if len(node.msgs) == 10: break time.sleep(0.1) assert len(node.msgs) == 10 assert node.msgs[-1] == Int32(data=45) @pytest.fixture def make_test_node(): rclpy.init() node = DummyTestNode() node.start() yield node node.destroy_node() rclpy.shutdown()
  135. Copyright © Fixstars Group test/launch/test_accum.py 135 class DummyTestNode(Node): def __init__(self):

    super().__init__("test_node") self._pub = self.create_publisher(Int32, "src", 10) self._sub = self.create_subscription(Int32, "dst", self._msg_received, 10) self.msgs = [] def _msg_received(self, msg): self.msgs.append(msg) def _wait_for_connect(self, timeout_s=5): end_time = time.time() + timeout_s while time.time() < end_time: cnt = self._pub.get_subscription_count() if cnt > 0: return True time.sleep(0.1) return False def start(self): self._ros_spin_thread = Thread( target=lambda node: rclpy.spin(node), args=(self,) ) self._ros_spin_thread.start() self._wait_for_connect() def publish(self, data): self._pub.publish(data)
  136. Copyright © Fixstars Group サンプルアプリでのテスト 実装 • 概要 • cpp_calc

    • py_accum • integration 136
  137. Copyright © Fixstars Group 概要 ⚫ twice と accum を組み合わせる

    → 2倍にして累積する ◦ launchファイルとして実装 ⚫ テストでは rosbag をテストデータとして使用する ⚫ 最終的なフォルダ構成 ◦ ament_cmake を使用 137 cpp_calc/twice node py_accum/accum node integration . ├── package.xml ├── CMakeLists.txt ├── launch │ └── integration.launch.yaml └── test ├── data │ └── testdata.bag └── launch └── test_integration.py
  138. Copyright © Fixstars Group パッケージ作成 138 <?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>integration</name> <version>1.0.0</version> <description>Integration package</description> <maintainer email="[email protected]">Shuhei Aoki</maintainer> <license>MIT</license> <buildtool_depend>ament_cmake_auto</buildtool_depend> <depend>py_accum</depend> <depend>cpp_calc</depend> <test_depend>ament_lint_auto</test_depend> <test_depend>ament_lint_common</test_depend> <test_depend>ament_cmake_pytest</test_depend> <test_depend>launch_pytest</test_depend> <export> <build_type>ament_cmake</build_type> </export> </package> launch_pytest を追加 ライセンス設定 ros2 pkg create --build-type ament_cmake integration package.xml lintの設定は cpp_calc と同様なので省略
  139. Copyright © Fixstars Group launchファイル 139 launch: - node: pkg:

    cpp_calc exec: twice remap: - from: /dst to: /calc_to_accum - node: pkg: py_accum exec: accum remap: - from: /src to: /calc_to_accum cpp_calc/twice node py_accum/accum node /src /dst /calc_to_accum launch/integration.launch.yaml
  140. Copyright © Fixstars Group launch integration test (launch_pytest) ⚫ rosbagをテストデータとして使用する

    ◦ 0 から 9 の値が入っている ▪ → integration が出力する最終値は 90 になるべき 140 if(BUILD_TESTING) ament_auto_find_test_dependencies() find_package(ament_cmake_pytest) ament_add_pytest_test(pytest test ) endif() CMakeLists.txt
  141. Copyright © Fixstars Group test/launch/test_integration.py 141 import pytest import launch_pytest

    from launch import LaunchDescription from launch.actions import IncludeLaunchDescription, ExecuteProcess from launch.launch_description_sources import AnyLaunchDescriptionSource from ament_index_python import get_package_share_directory from pathlib import Path import rclpy import rclpy.node from std_msgs.msg import Int32 from threading import Thread
  142. Copyright © Fixstars Group test/launch/test_integration.py 142 @pytest.fixture def integration_launch(): return

    IncludeLaunchDescription( AnyLaunchDescriptionSource( str( Path(get_package_share_directory("integration")) / "launch" / "integration.launch.yaml" ) ) ) @pytest.fixture def testdata(): path_to_test = Path(__file__).parent.parent return ExecuteProcess( cmd=[ "ros2", "bag", "play", str(path_to_test / "data" / "testdata.bag"), "--delay", "1", "--wait-for-all-acked", "1000", ], shell=True, ) launchファイルを起動する action ros2 bag play を実行する action • 起動してから1秒待ってから再生開始さ せる(humble以降) • 送ったデータが全て受信されたことを 確認してから終了する(humble以降)
  143. Copyright © Fixstars Group test/launch/test_integration.py 143 @pytest.fixture def receiver(): rclpy.init()

    node = ReceiverNode() node.start() yield node node.destroy_node() rclpy.shutdown() class ReceiverNode(rclpy.node.Node): def __init__(self): super().__init__("test_receiver") self.msgs = [] self._sub = self.create_subscription( Int32, "dst", lambda msg: self.msgs.append(msg), 10 ) def start(self): self._ros_spin_thread = Thread( target=lambda node: rclpy.spin(node), args=(self,) ) self._ros_spin_thread.start()
  144. Copyright © Fixstars Group test/launch/test_integration.py 144 @launch_pytest.fixture def generate_test_description(testdata, integration_launch):

    return LaunchDescription( [ testdata, integration_launch, ] ) @pytest.mark.launch(fixture=generate_test_description) async def test_should_be_90(testdata, receiver): await testdata.get_asyncio_future() yield assert receiver.msgs[-1] == Int32(data=90) LaunchDescription を作成 • testdata と integration_launch は fixture のもの • 2つ前のページで定義した関数の結果が得られる ros2 bag play のプロセスを得られる。 await testdata.get_asyncio_future() でプロセス終了待ち、つまりテスト データ再生終了待ちができる
  145. Copyright © Fixstars Group test/launch/test_integration.py 145 @launch_pytest.fixture def generate_test_description(testdata, integration_launch):

    return LaunchDescription( [ testdata, integration_launch, ] ) @pytest.mark.launch(fixture=generate_test_description) async def test_should_be_90(testdata, receiver): await testdata.get_asyncio_future() yield assert receiver.msgs[-1] == Int32(data=90) yield で一旦 launch が終了する。 launchが終了した後、yield 以降が実行される → launch終了後の状態を得られる set up test_xxx tear down launch PUT shutdown PUT each test cases post test_xxx
  146. Copyright © Fixstars Group その他TIPS

  147. Copyright © Fixstars Group TIPS ⚫ ラベルとマーカー ⚫ テスト時のROS_DOMAIN_ID ⚫

    CIでの自動テスト 147
  148. Copyright © Fixstars Group ラベルとマーカー ⚫ ctest のラベルと pytest のマーカーを使うことで実行するテストを絞ること

    ができる ◦ gtest だけ実行したい、lint だけ実行したい、といったケースに活用できる ⚫ ament_cmake では ament_add_test_label で ctest ラベルを付与 ◦ テストフレームワーク毎に自動で設定されているものもある ⚫ pytest のマーカーの例 ⚫ コマンド例 148 @pytest.mark.flake8 # ← flake8 マーカー @pytest.mark.linter # ← linter マーカー def test_flake8(): ... # ctest colcon test --ctest-args -L gtest # pytest colcon test --pytest-args -m copyright
  149. Copyright © Fixstars Group ROS_DOMAIN_ID ⚫ これまで紹介したテストは、1つ1つ独立して実行する場合問題ないが、同時 に実行すると問題が出るケースがある ◦ launch

    integration test ではトピック通信を扱っていたため、トピック名が重複すると通信が 混線する ▪ 確率で失敗する、という状態になる ⚫ この問題を回避するために ROS_DOMAIN_ID を使用したい ◦ → しかしどのようにROS_DOMAIN_IDを設定するか? 149
  150. Copyright © Fixstars Group ROS_DOMAIN_ID ⚫ domain_coordinatorを使用する ◦ https://github.com/ros2/ament_cmake_ros/tree/rolling/domain_coordinator ◦

    重複しないように ROS_DOMAIN_ID を選択する Python パッケージ ◦ 実装としてはソケットによるポートロックを応用したもので、使われている ROS_DOMAIN_IDを検知しているわけではないことに注意 150
  151. Copyright © Fixstars Group ROS_DOMAIN_ID ⚫ ament_cmake の場合 ◦ https://github.com/ros2/ament_cmake_ros/tree/rolling/ament_cmake_ros

    を使う ▪ 内部的に domain_coordinator を使用 ▪ gtest, gmock, pytest は専用の cmakeマクロがあるが、launch_testing にはないので、 launch_testing の場合 runner スクリプトだけ拝借する形になる 151 # 要 <test_depend>ament_cmake_ros</test_depend> find_package(ament_cmake_ros REQUIRED) add_launch_test(test/test_twice_launch.py RUNNER "${ament_cmake_ros_DIR}/run_test_isolated.py" ) CMakeLists.txt $ colcon test --packages-select example_launch_testing_ament_cmake --event-handlers console_direct+ ... 1: Running with ROS_DOMAIN_ID 1 ... 実行例
  152. Copyright © Fixstars Group ROS_DOMAIN_ID ⚫ ament_python の場合 ◦ domain_coordinator

    をPythonライブラリとして使う形になりそう ◦ 以下のようなコードを最初に書く 152 import domain_coordinator import contextlib import os stack = contextlib.ExitStack() if "ROS_DOMAIN_ID" not in os.environ and "DISABLE_ROS_ISOLATION" not in os.environ: domain_id = stack.enter_context(domain_coordinator.domain_id()) os.environ["ROS_DOMAIN_ID"] = str(domain_id) test/launch/test_twice_launch.py
  153. Copyright © Fixstars Group CIでの自動テスト ⚫ コードをコミットし、リモートリポジトリにプッシュする毎に自動的にテス トが実行されるようになると、継続的な検証が可能になる ⚫ ここでは

    GitLab CI/CDでの一例を示す ⚫ CIで行うこと ◦ ビルド/テスト環境の構築 ◦ ビルド/テスト 153
  154. Copyright © Fixstars Group CIでの自動テスト ⚫ ビルド/テスト環境の構築 ◦ 使用しているDockerfileをビルドしてイメージを GitLab

    のレジストリに登録 154 docker: image: docker:latest variables: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "/certs" services: - docker:dind stage: build script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build -t $CI_REGISTRY/tech/ros2-seminar/test-methodology/devenv:latest docker - docker push $CI_REGISTRY/tech/ros2-seminar/test-methodology/devenv:latest .gitlab-ci.yml (1/2)
  155. Copyright © Fixstars Group CIでの自動テスト ⚫ ビルド/テストの実行 ◦ 作ったイメージを使ってビルドとテストを実行 ◦

    CIの結果として、buildとinstall、logディレクトリを artifacts に指定 155 test: image: $CI_REGISTRY/tech/ros2-seminar/test-methodology/devenv:latest variables: PYTHONWARNINGS: ignore:::setuptools.command.install,ignore:::setuptools.command.easy_install script: - source /opt/ros/$ROS_DISTRO/setup.bash - colcon build - colcon test - colcon test-result --verbose artifacts: paths: - build - install - log expire_in: 1 week .gitlab-ci.yml (2/2)
  156. Copyright © Fixstars Group CIでの自動テスト ⚫ 実行例(抜粋) 156 GitLabの画面より

  157. Copyright © Fixstars Group 参考文献

  158. Copyright © Fixstars Group 参考文献 • colcon • https://colcon.readthedocs.io/en/released/index.html •

    https://github.com/colcon/colcon-core • https://github.com/colcon/colcon-cmake • ament • https://github.com/colcon/colcon-ros • https://github.com/ament/ament_cmake • https://github.com/ros2/ament_cmake_ros • https://github.com/ros2/launch • test • https://cmake.org/cmake/help/latest/manual/ctest.1.html • https://cmake.org/cmake/help/latest/command/add_test.html • https://github.com/google/googletest • http://opencv.jp/googletestdocs/primer.html • http://opencv.jp/googlemockdocs/fordummies.html • https://docs.pytest.org/en/stable • https://docs.python.org/ja/3/library/unittest.html • lint • https://github.com/ament/ament_lint • https://zenn.dev/yhay81/articles/yhay81-202102-pythonlint • https://siderlabs.com/blog/ja/python-lint-pickup-5tools/ • その他 • https://docs.gitlab.com/ee/ci/ 158
  159. Copyright © Fixstars Group Thank you! お問い合わせ窓口 : [email protected]