はじめに
- Bash のテストフレームワーク
bats
に関する記事 - bats ファイルのグローバルスコープ 1 に書いたコマンドが異常終了 (
$? -ne 0
) すると、bats も異常終了する話
bats とは?
Bash スクリプトのテストツールです。参考サイトを以下に挙げます。
起きた問題
bats コード
以下を行うだけのコードです。わかりやすくするために実際のテストコードは端折っています。
- env.sh を読み込む
- /tmp/buffer.txt を初期化する
#!/usr/bin/env bats
source ./env.sh
function setup() {
: > /tmp/buffer.txt
}
実行結果
想定していた処理結果は以下です。
$ ./test.bats
0 tests, 0 failures
$ echo $?
0
しかし、実際は以下のように処理に失敗してしまい、テストも行われませんでした。
$ ./test.bats
$ echo $?
1
原因
source ./env.sh
が原因でした。
具体的には、以下のように env.sh 内で開発環境か否かのチェックを行っていました。
hostname | grep 'dev'
if [ $? -eq 0 ]; then
# 開発環境向けの処理
else
# 本番環境向けの処理
fi
問題が発生するのは hostname | grep 'dev'
の実行結果が 1
となるとき、すなわち開発環境でないときです。
ここで bats は、グローバルスコープに記述したコマンドが正常終了しなかった場合に、 bats 自体も異常終了して落ちてしまうようです。
解決策
2つ考えられます。
ラップする方法
異常終了するコマンドを if でくくり、異常終了をラップします。
hostname | grep 'dev'
if [ $? -eq 0 ]; then
# 開発環境向けの処理
:
if hostname | grep 'dev'; then
# 開発環境向けの処理
:
後者の方がスマートでいいですね。
無理やりな方法
正常終了しないコマンドは || :
で無理やり正常終了させたことにします。
source ./env.sh || :
source
が失敗した場合は、 :
つまり「何もしない」コマンドが実行されて必ず正常終了を返します。
これによって、 source
がどちらに転んでも正常終了を返す仕組みを作ります。
ただ、エラーハンドリングという観点でみると杜撰なので、前者の if を使った方法を好ましく思います。
まとめ
- bats を使うときはコマンドの戻り値
$?
を意識する - 異常終了しそうなコマンドは if でラップするか、無理やり正常終了化させることで bats の強制終了を回避する
所感
- bats の思想に触れてみると、「スクリプト内のすべてのコマンドは 0 で終了すること」がシェルスクリプトの1つの理想形なのかな、と感じたりした
-
本記事では「
setup
やteardown
、@test
などのファンクション外」のことを指しています。もっとも、シェルスクリプトにおいてはどこでもグローバルと呼べそうな気もしますが… ↩