Batsを使ってシェルスクリプトをテストする
はじめに
この記事はシェルスクリプト&PowerShell Advent Calendar 2024の12日目として書かれています。
概要
BatsはBash用のテストフレームワークです。
何らかのプログラミング言語で書くまででもない簡単なスクリプトを書こうと思った際に、とはいえテストが欲しいと思って見つけたのがBatsでした。
今回は簡単なBatsのテストコードの紹介をします。
書いたコードはここに置いています。
今回使用したbatsコマンドはHomebrewで入れており、バージョンは1.11.0です。
> bats --version
Bats 1.11.0
シンプルなテストコード
Batsのテストコードはシェルスクリプトとは別のファイルに書き、batsコマンドでそのファイルを実行することでテストを実行できます。
テストケースは@test "test case name"
のように書けます。
#!/usr/bin/env bats
@test "echo hoge" {
result=$(echo hoge)
[ "$result" = "hoge" ]
}
@test "echo hoge expected to fuga" {
result=$(echo hoge)
[ "$result" = "fuga" ]
}
実行するとこのようになります。
> bats test_simple.bats
test_simple.bats
✓ echo hoge
✗ echo hoge expected to fuga
(in test file test_simple.bats, line 10)
`[ "$result" = "fuga" ]' failed
2 tests, 1 failure
run helperと特殊なグローバル変数
Batsにはrunというヘルパーコマンドが用意されており、これを使って関数を実行すると、いくつかのグローバル変数に結果が書き込まれます。
- $status
- 関数実行時のexit code
- $output
- 標準出力と標準エラー出力
- --separate-stderrをつけることで分けられる
- $lines
- 配列でoutputを行ごとに分けたものが入る
それらを使ってテストを書いてみるとこのようになります。
#!/usr/bin/env bats
@test "exit with 0 with status variable" {
run test 1 -eq 1
[ "$status" -eq 0 ]
}
@test "1 + 1 = 2 with output variable" {
run echo $((1+1))
[ "$output" -eq 2 ]
}
実行するとこのようになります。
> bats test_simple_global_vars.bats
test_simple_global_vars.bats
✓ exit with 0 with status variable
✓ 1 + 1 = 2 with output variable
2 tests, 0 failures
run helperのオプション
runコマンドにはオプションがあり、それらを使うとexit codeのassertionなどをオプションで済ませることができます。
#!/usr/bin/env bats
setup() {
# 1.5以上でrunにoptionを使えるようなのでそれを指定してWarningを回避しています
# BATS_VERSIONの環境変数に入れるでも問題ないようです
# @see https://github.com/bats-core/bats-core/blob/b640ec3cf2c7c9cfc9e6351479261186f76eeec8/lib/bats-core/common.bash#L55
bats_require_minimum_version 1.5.0
}
@test "(expected to fail) exit with non 0 with ! option" {
run ! -- test 1 -eq 1
}
@test "(expected to fail) exit with non 0 with -N option" {
run -1 -- test 1 -eq 1
}
実行するとこのようになります。
結果がわかりやすいように失敗させています。
> bats test_simple_run_helper.bats
test_simple_run_helper.bats
✗ (expected to fail) exit with non 0 with ! option
(in test file test_simple_run_helper.bats, line 11)
`run ! -- test 1 -eq 1 ' failed, expected nonzero exit code!
✗ (expected to fail) exit with non 0 with -N option
(in test file test_simple_run_helper.bats, line 15)
`run -1 -- test 1 -eq 1 ' failed, expected exit code 1, got 0
2 tests, 2 failures
タグを付けての実行
コメントでタグをつけることができます。
そのタグのみを指定して実行できるようです。
#!/usr/bin/env bats
# bats test_tags=1digits
@test "1 + 1 = 2" {
run echo $((1+1))
[ "$output" -eq 2 ]
}
# bats test_tags=2digits
@test "10 + 15 = 25" {
run echo $((10+15))
[ "$output" -eq 25 ]
}
実行するとこのようになります。
bats --filter-tags 1digits ./test_tags.bats
test_tags.bats
✓ 1 + 1 = 2
1 test, 0 failures
> bats --filter-tags 2digits ./test_tags.bats
test_tags.bats
✓ 10 + 15 = 25
bats:focusタグを使って一つだけテストを実行する
#!/usr/bin/env bats
# bats test_tags=bats:focus
@test "focus echo" {
run echo hoge
[ "$output" = "hoge" ]
}
@test "not focus echo fuga" {
run echo fuga
[ "$output" = "fuga" ]
}
実行するとこのようになります。
全体のテストを行わず一部機能のリファクタリングなどに使う用途を意図してか、意図的に成功とするようにしないと正常終了しないようになっています。
CIなどで全体のテストが回らずに通ってしまうことは起きないようですね。
> bats ./test_tags_focus.bats
WARNING: This test run only contains tests tagged `bats:focus`!
1..1
suite /path/totest-with-bats/test_tags_focus.bats
begin 1 focus echo
ok 1 focus echo
Marking test run as failed due to `bats:focus` tag. (Set `BATS_NO_FAIL_FOCUS_RUN=1` to disable.)
関数を読み込んだ上でのテスト
関数をまとめて別ファイルに切り出しておき、それのテストを行ってみます。
setupで読み込み、今まで通りテストするだけです。
functions.sh
get_day_of_week() {
echo "${TEST_DAY:-$(gdate +%u)}"
}
should_run_main() {
local day_of_week=$(get_day_of_week)
# 月曜1 ~ 日曜7
if [[ "$day_of_week" -eq 6 || "$day_of_week" -eq 7 ]]; then
return 1
fi
return 0
}
main() {
if ! should_run_main; then
return
fi
echo "main is running"
}
test_functions.bats
#!/usr/bin/env bats
setup () {
source "./functions.sh"
}
@test "on Monday should_run_main returns 0" {
TEST_DAY=1
run should_run_main
[ "$status" -eq 0 ]
}
@test "on Sunday should_run_main returns 1" {
TEST_DAY=7
run should_run_main
[ "$status" -eq 1 ]
}
実行するとこのようになります。
> bats test_functions.bats
test_functions.bats
✓ on Monday should_run_main returns 0
✓ on Sunday should_run_main returns 1
2 tests, 0 failures
備考(assert_outputなど)
今回は使いませんでしたが、assertionなどのヘルパーもあるようです。
まとめ
- シェルスクリプトを書いたときにテストするならBatsが便利
規模が大きくなるならGoとかのほうがいいのでは- 簡単に書けるのでちょっと雑に書いたスクリプトなどで使っていきたい
Discussion