シェルスクリプトをコーディングする機会があり、単体テストをするためにテストフレームワークを調べてみた。
シェルスクリプトにもテストフレームワークがある
JavaであればJUnit、PythonにはPyUnit、C/C++であればGoogleTestのように各プログラミング言語にはテストフレームワークがあるように、シェルスクリプトにもテストフレームワークが存在する。
メジャーなテストフレームワークは以下の3つだと思う。
(私が勝手にこの3つだけだと思っているかも)
Bats
Shellspec
shUnit2
Bats
現在も定期的にリリースされているテストフレームワーク。
bats とは、Bash Automated Testing System のこと。
ドキュメントも用意されている。
尊敬しているカックさんもブログで記事していた。
[http://kakakakakku.hatenablog.com
Shellspec
現在もBugFixしたものがリリースされている。
BDDスタイルのテストフレームワーク。
他のテストフレームワークに比べると飛び抜けて高機能。
作成者ご本人がShellspecの開発の意図や設計などについて書かれている。
shUnit2
xUnitのテストフレームワーク。
JUnitやPyUnitと同様の手法で動作するように設計されている。
お馴染みの「assertEquals」などが用意されている。
2020年4月に2.1.8がリリースされているが、その前が2018年とほぼメンテナンスされていない。
それぞれをかじってみる
加算する関数をテスト対象として、テストを作成してみる。
#!/bin/bash function add() { if [[ $# -ne 2 ]] ; then echo "Argument Error!!" 1>&2 return 1 fi local a local b local c a=$1 b=$2 c=$((a + b)) echo "${c}" return 0 }
Bats
まずは、Batsをセットアップする。
Githubからリポジトリをクローンしてインストーラを実行するとセットアップは完了する。
インストーラのパラメータにインストール先を指定することが可能で、/usr/localにインストールしたい場合は 、install.sh のパラメータに /usr/local を指定する。
今回は自分のユーザ配下にBatsをインストールした。
$ git clone https://github.com/bats-core/bats-core.git $ cd bats-core $ ./install.sh $HOME
Batsのテストソースは以下のようになる。
setup関数でテスト対象の関数を実装しているシェルスクリプトのファイルをロードする。
テスト対象の関数を実行すると、status変数に終了コード、output変数に標準出力の値が設定される。
標準出力は、1行単位から確認することも可能。
テストの実行は bats <テストソース>
で実行ができる。
#!/usr/bin/env /home/vagrant/bin/bats setup() { . shell_func.sh } @test "add_success" { # Expected Define local exp_exit_code=0 local exp_result=30 # Test run add 10 20 # Verify [ "${status}" -eq ${exp_exit_code} ] [ "${output}" == "${exp_result}" ] } @test "add_parameter_error" { # Expected Define local exp_exit_code=1 local exp_err_msg="Argument Error!!" # Test run add 10 # Verify [ "${status}" -eq ${exp_exit_code} ] [ "${output}" == "${exp_err_msg}" ] }
Shellspec
セットアップ方法がいくつか公開されているが、Batsと同じようにユーザのホームディレクトリにセットアップする。
Githubのリポジトリをクローンし、パスが通っているディレクトリにシンボリックリンクを作成する。
$ cd $HOME $ git clone git clone https://github.com/shellspec/shellspec.git $ ln -s $HOME/shellspec/shellspec $HOME/bin/shellspec
shellspec --initでプロジェクトを作成して、specフォルダにテストソースを作成する。
$ shellspec --init $ cd spec
テストソースはIncludeにテスト対象のシェルスクリプトファイルを定義する。
Batsと同じにようにテスト対象の関数の終了コードはstatusに、標準出力はoutputに格納されている。
specフォルダ直下でshellspec
コマンドを実行すれば*_spec.shのテストコードが実行される。
Describe 'shell func' Include ../shell/shell_func.sh It 'add func' When call add 10 20 The status should equal 0 The output should equal 30 End End Describe 'shell func' Include ../shell/shell_func.sh It 'add error' When call add 10 The status should equal 1 The error should equal "Argument Error!!" End End
shunit2
セットアップ方法は、一番シンプル。
Githubからリポジトリをクローンするだけでセットアップは完了。
$ git clone https://github.com/kward/shunit2.git
テストソースもJUnitのテストソースを書いたことがある人は、馴染みのあるテストソースになる。
確認用の関数が用意されているので、その関数を使用して確認をする。
BatsやShellspecのように終了コードが指定の変数に格納されないため、自分で終了コードを格納する必要がある。
#!/bin/bash function test_add_success() { # Expected Define local exp_exit_code=0 local exp_result=30 # Test local act_result=$(add 10 20) local act_exit_code=$? # Verify assertEquals ${exp_exit_code} ${act_exit_code} assertEquals ${exp_result} ${act_result} } function test_add_param_error() { # Expected Define local exp_exit_code=1 local exp_error_msg="Argument Error!!" # Test act_msg=$(add 10 2>&1) act_exit_code=$? # Verify assertEquals "Exit Code unmatch" ${exp_exit_code} ${act_exit_code} assertEquals "Error Msg unmatch" "${exp_error_msg}" "${act_msg}" }
どれが使いやすい のか?
正直、どれも使いやすい。
学習コストを抑えて、即時適用したいのであれば、shUnit2が最もシンプルかと思う。
ただ、1~2日の学習コストが取れるのであれば、shUnit2よりもbatsがおすすめ。
標準出力されるメッセージを検査するとなると、shUnit2だとメッセージをまるまる比較しないといけないため、複数行になると期待値を作成するのが大変だが、Batsでは標準出力の結果を配列で保持してくれているので、1行ずつ確認することができる。
Shellspecは高機能なため、1時間ぐらいでサクッとテストが作成できる気がしない。
実際に、業務のツールのテストソースを作成してみたが、サクッと作れなかった。BDDのテストソースを作ったことがないので、作成の容量が掴めなかったのもあるかもしれない。
それに比べて、shUnit2は非常にシンプルなのでJUnitを作ったことがあれば、サクッとできるはず。
ただ、高機能なので学習コストを1~3日取れば、リターンは大きいと思う。
kcovが必要になるが、カバレッジも取れて、JUnit XML formatterでもテスト結果の出力ができることは魅了的だ。(BatsもJUnit XML formatterは対応している。)
結局のところ、どれがいいとははっきりと言えないが、どれもテストフレームワークとして十分な機能がある。
あとは学習コストやテスト作成のスケジュール、今後のメンテナンスを考えたときに何を使うべきかの判断になるだろう。