テストの自動化ツールAutotest.mkを作って使ってみた話
成功と言えるかどうかはわかりませんが、とりあえず作っていて楽しかったことだけは確かなので、自作のテストツールAutotest.mkを紹介します。
Autotest.mkとは
Autotest.mkは、GNU Makeを利用したソフトウェアテスト自動化ツールです。
詳細は、Autotest.mkのページを参照してください。GNU Makeで実装している点が最大の特徴だと考えています。
ソフトウェア・テストの古典である『ソフトウェア・テストの技法』に記載されている「三角形判定プログラムのテスト」、通称「Myersの三角形判定問題」をもとにしたチュートリアルも用意しています。
Qiitaの自作エディタをつくる Advent Calendar 2016の記事コンソールで動くエディタを作る(20日目) テストフレームワークレス・テストでは、「与えられたコマンドを全て実行して、標準出力の結果をテストケースとして書かれたテキストファイルと比べる。一致していたらテストが通ったとみなす。」と紹介されています。メリットとデメリットも適切に紹介されていると思います。
内部的には、テストケースとして書かれたテキストファイルをあらかじめ0.txtファイルとして用意してもらい、標準出力の結果を1.txtファイルに出力し、0.txtと1.txtをdiff
で比較して結果を出力する、という方法になっています。テストの結果は、サマリー版と詳細ログの2種類の形で記録されます。
Autotest.mk作成のきっかけ
Autotest.mk作成のきっかけは、CASL II処理システムであるYACASL2の開発でした。YACASL2開発の経緯については、C言語の勉強がてら、CASL II処理システムを実装した話を参照してください。このYACASL2開発の中で、テストの効率化に向き合うことになったのです。
テスト、なかでもレグレッションテストの重要性については、『プログラミング作法』を読んでかねてより感じていました。この本では、優れたテストの実例としてプログラミング言語Perlのレグレッションテストを挙げていました。同様のテストを、CASL II言語処理システムであるYACASL2でもできるはずだと調べはじめたのです。
調べて最初に目についたのは、GNU Test FrameworkのDejaGnuでした。GNUの各種ツール、たとえばcp
やls
などといったUnix系OSの基本コマンドを提供するCoreutilsでは、ソースコードのあるディレクトリーでmake check
コマンドを実行するとレグレッションテストを実施できます。こうしたテストを実現しているのがDejaGnuということで俄然興味が湧きました。しかし、DejaGnuは使い方がよくわからない、日本語ではあまり良い情報がないらしい、ということで悶々とすることになります。そんなある日、どうせなら自分でテストフレームを作ればいいじゃないかというアイデアが頭に浮かび、Autotest.mkは生まれました。作ってみたら割と簡単でした。
YACASL2のテスト
YACASL2では、make check
でAutotest.mkを使った300くらいのテストを実行します。重点的にテストするのは、コマンド(casl2、comet2、casl2rev、dumpword、comet2monitor)が正しく動作するか、コマンドラインオプション(casl2_optやcomet2_optなど)が正確に機能するか、LD、ST、LADなどといったCASL IIの命令を含むプログラムを正しく実行できるか、エラー時の動作、といった点です。実行時の様子は次の通りです。
~/yacasl2 [12:26:20] $ make check
make -sC test/system
/Users/kazubito/Documents/yacasl2/test/system/casl2_opt - clean
/Users/kazubito/Documents/yacasl2/test/system/casl2_opt - 12 tests
casl2_opt: 12 / 12 tests passed. Details in /Users/kazubito/Documents/yacasl2/test/system/casl2_opt/CASL2_OPT.log
casl2_opt: All tests are succeded.
/Users/kazubito/Documents/yacasl2/test/system/casl2_cmd - clean
/Users/kazubito/Documents/yacasl2/test/system/casl2_cmd - 143 tests
casl2_cmd: 143 / 143 tests passed. Details in /Users/kazubito/Documents/yacasl2/test/system/casl2_cmd/CASL2_CMD.log
casl2_cmd: All tests are succeded.
/Users/kazubito/Documents/yacasl2/test/system/casl2_err - clean
/Users/kazubito/Documents/yacasl2/test/system/casl2_err - 31 tests
casl2_err: 31 / 31 tests passed. Details in /Users/kazubito/Documents/yacasl2/test/system/casl2_err/CASL2_ERR.log
casl2_err: All tests are succeded.
/Users/kazubito/Documents/yacasl2/test/system/comet2_smoke - clean
/Users/kazubito/Documents/yacasl2/test/system/comet2_smoke - 2 tests
comet2_smoke: 2 / 2 tests passed. Details in /Users/kazubito/Documents/yacasl2/test/system/comet2_smoke/COMET2_SMOKE.log
comet2_smoke: All tests are succeded.
/Users/kazubito/Documents/yacasl2/test/system/comet2_opt - clean
/Users/kazubito/Documents/yacasl2/test/system/comet2_opt - 6 tests
comet2_opt: 6 / 6 tests passed. Details in /Users/kazubito/Documents/yacasl2/test/system/comet2_opt/COMET2_OPT.log
comet2_opt: All tests are succeded.
/Users/kazubito/Documents/yacasl2/test/system/comet2_cmd - clean
/Users/kazubito/Documents/yacasl2/test/system/comet2_cmd - 141 tests
comet2_cmd: 141 / 141 tests passed. Details in /Users/kazubito/Documents/yacasl2/test/system/comet2_cmd/COMET2_CMD.log
comet2_cmd: All tests are succeded.
/Users/kazubito/Documents/yacasl2/test/system/comet2_err - clean
/Users/kazubito/Documents/yacasl2/test/system/comet2_err - 11 tests
comet2_err: 11 / 11 tests passed. Details in /Users/kazubito/Documents/yacasl2/test/system/comet2_err/COMET2_ERR.log
comet2_err: All tests are succeded.
/Users/kazubito/Documents/yacasl2/test/system/casl2rev - clean
/Users/kazubito/Documents/yacasl2/test/system/casl2rev - 2 tests
casl2rev: 2 / 2 tests passed. Details in /Users/kazubito/Documents/yacasl2/test/system/casl2rev/CASL2REV.log
casl2rev: All tests are succeded.
/Users/kazubito/Documents/yacasl2/test/system/dumpword - clean
/Users/kazubito/Documents/yacasl2/test/system/dumpword - 80 tests
dumpword: 80 / 80 tests passed. Details in /Users/kazubito/Documents/yacasl2/test/system/dumpword/DUMPWORD.log
dumpword: All tests are succeded.
/Users/kazubito/Documents/yacasl2/test/system/comet2monitor - clean
/Users/kazubito/Documents/yacasl2/test/system/comet2monitor - 11 tests
comet2monitor: 11 / 11 tests passed. Details in /Users/kazubito/Documents/yacasl2/test/system/comet2monitor/COMET2MONITOR.log
comet2monitor: All tests are succeded.
テストの分類
こうしたYACASL2のテストは、一般的なテスト分類であるユニットテスト・結合テスト システムテストの中でシステムテストにあたると考えています。ユニットテストや結合テストは、YACASL2では一部だけ用意した形になっています。システムテストさえ通れば、ユニットテストや結合テストは不要だと考えました。この辺りは個人開発の手軽さもあると思っています。一方で、システムテストはできるだけ網羅的に実行したいと考えました。
テストはリグレッション(回帰)テストの形で行うことで、プログラムの修正によってそれまで正常に動作していた機能が動作しなくなることをチェックできるとともに、テストを網羅的に実行できます。
情報処理試験あたりでテストについて学んだとき、テストの種類として、次のものが出てきました。
- ユニットテスト
- 結合テスト
- システムテスト
- 受け入れテスト
- リグレッションテスト
このうち、ユニットテスト・結合テスト・システムテスト・受け入れテストがプログラム開発の進行に応じた分類だというのは理解できたのですが、リグレッションテストの位置付けがよくわかりませんでした。Autotest.mkを使ったYACASL2のテストを通じて学んだことは、網羅的なテストを行うためにはリグレッションテストを行うということでした。YACASL2では、システムテスト兼受け入れテストの段階でリグレッションテストが必要だと判断し、Autotest.mkで実装しました。
テストの手間と時間
AUtotest.mkを使って作成したYACASL12のテストは、実行までの手間はmake check
コマンド一発でできるのでラクです。しかし、実行完了までに時間がかかるのがややネックです。手元(2020年型MacBookAir、プロセッサintel Corei5、メモリ16GB)で試したところ、4分かかりました。ちょっと長く感じます。
一方で、ソフトウェアに求められる機能を漏れなくテストできているのかという心配も常につきまとうわけで、時間がかかるのはある程度仕方がないのかという思いもあります。この辺りは、上手なテストケースの作り方といった奥深い話につながりそうです。
まとめ
Autotest.mkというツールを使えば、リグレッションテストなどのテストの自動化ができます。よければお試し下さい。