TL;DR
Python用のROSパッケージで単体テストを書き,industrial_ci
の .travis.yml
を使ってTravisを通すには .travis.yml
の変数に CATKIN_CONFIG="--no-install
を追加しないといけない.
うまくいったときの様子(rostest,nosetests).
キーワード
- Python
- rostest
- Travis CI,
industrial_ci
- unittest
- nosetests
- Ubuntu 16.04, ROS Kinetic
- Ubuntu 18.04, ROS Melodic
bagmetti という,bagファイルのメッセージをフィルタしたり中身を変更したりする便利なパッケージを以前作りました.これに単体テストを書きたい.ついでに,ローカル環境はROS MelodicなのでKineticでもビルドできることが確認できるといいなーと思い,Travisを使います(とはいってもPythonなのでコンパイルは不要だし,外部ライブラリに依存していないので問題ないはずなんですが).
rostestを使った単体テストの実装
ROSは,Pythonの単体テストに関するドキュメントが非常に残念であることに気がつきました.公式のWikiが5年以上前の情報だったり,そもそもstackoverflowとかであまりヒットしない.
とはいうものの,Pythonのunittestのドキュメントは充実しています.rostestを使うのが良さそうだったので,テストケースを軽快に書いていきます.ローカル環境で rostest
を実行してpassしたので,これでCIに投げて一段落!と思いきや……
テストが通らない!
industrial_ci
というROSのためのCIテンプレートがあったので,それをベースに .travis.yml
を書いていきます.で,早速pushしてみたところ,見事にエラーが!
まぁCIでいきなりうまくいくことはないかと思いつつ,修正を加えるのですが,なかなか通らない.どうもテストコードで import
している自前のモジュールが見つかっていないらしい.
======================================================================
ERROR: Failure: ImportError (No module named bagmetti.rules.rename)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/nose/loader.py", line 418, in loadTestsFromName
addr.filename, addr.module)
File "/usr/lib/python2.7/dist-packages/nose/importer.py", line 47, in importFromPath
return self.importFromDir(dir_path, fqname)
File "/usr/lib/python2.7/dist-packages/nose/importer.py", line 94, in importFromDir
mod = load_module(part_fqname, fh, filename, desc)
File "/home/naoki/ros/workspaces/melodic/tmp/src/bagmetti/test/test_rename.py", line 8, in <module>
from bagmetti.rules.rename import RenameRule
ImportError: No module named bagmetti.rules.rename
たしかにローカル環境でも,一旦 build
や devel
を消してから catkin run_tests
を実行すると,これが起こる.
どうもこの問題は既知らしく,wikiで触れられていました.
One example is a rostest being run as part of the tests. If the test itself depends on Python code which is e.g. being generated the path containing the generated code is not necessarily on the PYTHONPATH. catkin_make does not provide a mechanism to work around this problem
じゃあどうすれば良いかというと,ビルドと実行を分けてやりなさいということでした. industrial_ci
は,ちゃんとそうしている模様なので問題なし.
それなのになぜモジュールが見つからないのかとハマりまくった結果,最終的に catkin
の設定で Install Location
が指定されていることが分かりました.インストールされる場所が想定と違うので,見つからなかったのか〜と半分働いていない頭で考えているのが現状.
問題の対策
対策は,単純に CATKIN_CONFIG
変数に --no-install
を渡してやるというものでした.だいぶ無駄な時間を費してしまったので,だれかの役に立つことを願っています.
nosetestsを使った単体テスト
ああでもないこうでもないと試行錯誤している過程で,nosetestsを使ったらできるんじゃないかと思い,nosetestsでもやってみました.
rostestがROS独自の配信や購読といった仕組みを念頭に置いたテストができるようになっているのに対し,noseは他ノードとやりとりしないようなスクリプトの単体テストに向いています.bagmettiは後者の使い方が多いので,noseでも良いかなーと思った次第です.
結論としては,nosetestsでもrostestでも, --no-install
さえ渡してやればどちらでもテストは動くということが分かりました.でも,nosetestsのほうがそっけないです.
nosetests
........
----------------------------------------------------------------------
Ran 8 tests in 0.170s
OK
rostest
[ROSTEST]-----------------------------------------------------------------------
SUMMARY
* RESULT: SUCCESS
* TESTS: 0
* ERRORS: 0
* FAILURES: 0
rostest log file is in /home/naoki/.ros/log/rostest-nazousagi-1694.log
[Testcase: testtest_filter] ... ok
[ROSTEST]-----------------------------------------------------------------------
[bagmetti.rosunit-test_filter/test_is_exclude][passed]
[bagmetti.rosunit-test_filter/test_is_include][passed]
[bagmetti.rosunit-test_filter/test_is_tf][passed]
[bagmetti.rosunit-test_filter/test_is_time][passed]
[bagmetti.rosunit-test_filter/test_is_topic][passed]
[bagmetti.rosunit-test_filter/test_match_tf][passed]
[bagmetti.rosunit-test_filter/test_match_time][passed]
[bagmetti.rosunit-test_filter/test_match_topic][passed]
SUMMARY
* RESULT: SUCCESS
* TESTS: 8
* ERRORS: 0
* FAILURES: 0
無事,テストが通るようになりました.みなさんもぜひ楽しいTravis + ROS + ユニットテスト生活を!