恥ずかしながら今まではシェルスクリプトで書いたプログラムのテストをするときは、echo
やprint
、-x
オプションを駆使して行なっていたのですが、Batsというテストフレームワークがあることを知り早速使ってみました。
2017.9.7修正 コード他
Bats概要
Bashで動作するソフトウェアのテストフレームワークでBashで書かれています。テスト対象は、BashスクリプトだけでなくUnix上で動作するプログラムなら何でも可能なようです。というのはソースを見ると分かりますが、BatsはBashから任意のプログラムを実行しその結果の正当性(終了ステータスや出力)を検証できるような作りになっています。
早速インストールしてテストコードを書いてみます。MacでHomeBrewを使っている場合は、以下でインストール可能です。
1 |
brew install bats |
実際にテストコードが動作する様子を見てみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#!/usr/bin/env bats @test "1+1 should equal to 2" { [ "$((1+1))" -eq 2 ] } @test "When test.sh is passed 'hoge', it should output 'hoge'" { run ./test.sh "hoge" [ $status -eq 0 ] [[ $output =~ hoge ]] } @test "When test.sh doesn't have arguments, it should show usage" { run ./test.sh [ $status -eq 1 ] local usage="usage: test.sh <message>" [[ $output =~ $usage ]] } @test "Whey say function is passed 'hello', it should output 'hello'" { source ./functions.sh run say "hello" [ $status -eq 0 ] [ "${lines[0]}" = "hello" ] } |
1 2 3 4 5 6 7 8 |
#!/usr/bin/env bash if [ $# -lt 1 ]; then echo "usage: test.sh <message>" exit 1 fi echo "$1" |
1 2 3 4 5 |
#!/usr/bin/env bash function say() { echo "$1" } |
1 2 3 4 5 6 |
✓ 1+1 should equal to 2 ✓ When test.sh is passed 'hoge', it should output 'hoge' ✓ When test.sh doesn't have arguments, it should show usage ✓ Whey say function is passed 'hello', it should output 'hello' 4 tests, 0 failures |
シンタックス
テストコードのシンタックスは以下のようなになります。body部分はBashスクリプトとして実行されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# OK @test "message" { # <body> bash script } # NG @test "message foo" { # @testに続く文字列は@testと同じ行でなければならない } # NG @test "hoge" { # { は@testと同じ行でなければならない } # NG @test "message"{ # "message"の前後にはスペースが必要 } |
setup、teardown
XUnitフレームワークのように、各テストブロック実行前後にsetup、teardownで定義された関数を実行することができます。
1 2 3 4 5 6 7 |
setup() { mkdir ./tmp } teardown() { rm -rf ./tmp } |
run
テストブロックの中では、run
という関数が使えます。runは、任意のスクリプトやコマンド、関数を指定することができます。終了ステータスは$status
、標準出力・標準エラー出力の結果は$output
という変数で参照できます。また、$lines
という変数には改行区切りで配列参照が可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@test "inline code" { run echo "hoge" [ "$output" = "hoge" ] } @test "function" { run say "hello" [ "$output" = "hello" ] } @test "script" { run ./test.sh "hoge" [ "$status" -eq 0 ] [ "$output" = "hoge" ] } |
skip
skip
を使うと後続のコマンドを実行せずに次のテストに移ります。
1 2 3 4 5 6 7 8 |
@test "skip" { skip false } @test "executed" { true } |
実行すると以下のようになります。
1 2 3 4 5 |
$ bats skip.bats - skip (skipped) ✓ executed 2 tests, 0 failures, 1 skipped |
load
load
関数を実行すると、任意のスクリプトをsource
コマンドで読み込みます。拡張子は、.bash
で指定する必要があります。
1 2 3 |
include() { [[ $output =~ $1 ]] } |
1 2 3 4 5 6 7 8 |
setup() { load test_helper } @test "include 'hoge'" { run echo "hoge" include "hoge" } |
どのように実行されている?
test.batsのコードは、実際には以下のようなシェルスクリプトに変換されて実行されているようです。$TMPDIR
を見ると実際のスクリプトを確認することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#!/usr/bin/env bats test_1-2b1_should_equal_to_2() { bats_test_begin "1+1 should equal to 2" 3; [ "$((1+1))" -eq 2 ] } test_When_test-2esh_is_passed_-27hoge-27-2c_it_should_output_-27hoge-27() { bats_test_begin "When test.sh is passed 'hoge', it should output 'hoge'" 7; run ./test.sh "hoge" [ $status -eq 0 ] [[ $output =~ hoge ]] } test_When_test-2esh_doesn-27t_have_arguments-2c_it_should_show_usage() { bats_test_begin "When test.sh doesn't have arguments, it should show usage" 13; run ./test.sh [ $status -eq 1 ] local usage="usage: test.sh <message>" [[ $output =~ $usage ]] } test_Whey_say_function_is_passed_-27hello-27-2c_it_should_output_-27hello-27() { bats_test_begin "Whey say function is passed 'hello', it should output 'hello'" 20; source ./functions.sh run say "hello" [ $status -eq 0 ] [ "${lines[0]}" = "hello" ] } bats_test_function test_1-2b1_should_equal_to_2 bats_test_function test_When_test-2esh_is_passed_-27hoge-27-2c_it_should_output_-27hoge-27 bats_test_function test_When_test-2esh_doesn-27t_have_arguments-2c_it_should_show_usage bats_test_function test_Whey_say_function_is_passed_-27hello-27-2c_it_should_output_-27hello-27 |
ドキュメントにもあるように、.batsファイルのスクリプトはn+1
回評価されます(n+1回、sourceコマンドで読み込みされます)。まず、上記の変換したファイルがsource
コマンドで読み込まれ、続いてbats_test_function
で実行するテスト関数が収集されます。この時、テスト関数は実行されません。続いて、bats_test_function
で登録された各テスト関数がsource
コマンドで読み込みされlibexec/bats-exec-test
で実行されます。関数ごとにbats-exec-test
コマンドを実行しますので、ある関数で設定した変数は、別の関数では参照できませんし、あるテストの設定が他のテストに影響しません。グルーバルな変数を使いたい場合は、@test{}
の外に書く必要があります。
実際に確認してみます。echo "hoge"
は、3回評価されるはずです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
echo "hoge" >> output.txt setup() { echo "setup ${BATS_TEST_NAME}" >> output.txt } teardown() { echo "teardown ${BATS_TEST_NAME}" >> output.txt } @test "test" { true } @test "test2" { true } |
これを実行すると、以下のような出力が得られます。テスト関数+1回分評価されていることが確認できました。
1 2 3 4 5 6 7 8 |
$ cat output.txt hoge hoge setup test_test teardown test_test hoge setup test_test2 teardown test_test2 |
その他
何故か以下のようにダブルブラケットを連続して書いた場合、最後の式以外の結果が打倒でないにも関わらずテストをパスしてしまいました。。原因は追えていないので何とも言えませんが、とりあえず&&
で対処してみました。
(2017.9.7 修正 “”の記述が不要でした) 2017.10.2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@test "this test should be failed" { run echo "hoge" [[ $output =~ foo ]] [[ $output =~ hoge ]] } @test "this test should be failed2" { run echo "hoge" [[ $output =~ foo ]] && [[ $output =~ hoge ]] } # ----------------------------------- # Results ✗ this test should be failed (in test file test.bats, line 3) `[[ $output =~ foo ]]' failed ✗ this test should be failed2 (in test file test.bats, line 8) `run echo "hoge"' failed 2 tests, 2 failures |
参考リンク
- https://github.com/sstephenson/bats
- http://qiita.com/5t111111/items/c4a382c7dd896c353d03
オライリージャパン
売り上げランキング: 119,456
関連
Batsに影響を受けているのでインタフェースは似ていますが、実行アプローチは異なるテストツールを書きました。BAsh Unit test ToolでBaut(バウト)です。
コメント