この記事は【2016年5月8日】と作成から2年以上経っているため、記事の内容が古い可能性があります。最新の情報を合わせてご確認されることを推奨いたします。
恥ずかしながら今まではシェルスクリプトで書いたプログラムのテストをするときは、echo
やprint
、-x
オプションを駆使して行なっていたのですが、Batsというテストフレームワークがあることを知り早速使ってみました。
2017.9.7修正 コード他
Bats概要
Bashで動作するソフトウェアのテストフレームワークでBashで書かれています。テスト対象は、BashスクリプトだけでなくUnix上で動作するプログラムなら何でも可能なようです。というのはソースを見ると分かりますが、BatsはBashから任意のプログラムを実行しその結果の正当性(終了ステータスや出力)を検証できるような作りになっています。
早速インストールしてテストコードを書いてみます。MacでHomeBrewを使っている場合は、以下でインストール可能です。
実際にテストコードが動作する様子を見てみます。
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" ]
}
#!/usr/bin/env bash
if [ $ # -lt 1 ]; then
echo "usage: test.sh <message>"
exit 1
fi
echo "$1"
#!/usr/bin/env bash
function say ( ) {
echo "$1"
}
✓ 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で定義された関数を実行することができます。
setup ( ) {
mkdir . / tmp
}
teardown ( ) {
rm - rf . / tmp
}
run
テストブロックの中では、run
という関数が使えます。runは、任意のスクリプトやコマンド、関数を指定することができます。終了ステータスは$status
、標準出力・標準エラー出力の結果は$output
という変数で参照できます。また、$lines
という変数には改行区切りで配列参照が可能です。
@ 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
を使うと後続のコマンドを実行せずに次のテストに移ります。
@ test "skip" {
skip
false
}
@ test "executed" {
true
}
実行すると以下のようになります。
$ bats skip .bats
- skip ( skipped )
✓ executed
2 tests , 0 failures , 1 skipped
load
load
関数を実行すると、任意のスクリプトをsource
コマンドで読み込みます。拡張子は、.bash
で指定する必要があります。
include ( ) {
[ [ $output = ~ $ 1 ] ]
}
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回分評価されていることが確認できました。
$ 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
Cameron Newham Bill Rosenblatt
オライリージャパン
売り上げランキング: 119,456
関連
Batsに影響を受けているのでインタフェースは似ていますが、実行アプローチは異なるテストツールを書きました。BAsh Unit test ToolでBaut(バウト)です。