概要
ファイル操作をするような bash スクリプトを書くことって多いですよね。
そんなとき、単体テストが書けると便利ですね。
ここでは diff を使って簡単にテストする方法を紹介します。
結論
- スクリプトをテスト可能に書く
- ファイルパスはすべて相対パスで記述(本番とテスト環境とで起点となるパスを変えられるように)
- 本日の日付は外部から渡す
- テストに使うファイル群を用意する
- data: 処理開始時点でのファイル群
- expected: 結果としてあるべきファイル群
- テストの際には:
- ひな型から work/actual, work/expected を生成
- テストケースに応じて work の内容変更
- work/actual エリアでスクリプト実行
- work/actual と work/expected の diff をとって比較
これらを便利にする関数を定義して可読性を上げます。
具体的に
ファイル構成
-
some_action.sh
というスクリプトがあるとして、それをテストする場合のファイル構成です。
some_action.sh # テスト対象のスクリプト
tests/
_common.sh # テストで使う共通スクリプト
some_action/
data/ # テストで使うファイル群
1-init/ # 初期状態
2-expected/ # 最終的な期待値
test.sh # テスト実行ファイル
実行
cd tests/some_action
./test.sh
各ファイルの中身
some_action.sh
if [ -f aaa.txt ]; then
mv aaa.txt bbb.txt
else
echo "ccc" > ccc.txt
fi
test.sh
#!/bin/bash
set -eu
source ../_common.sh
TARGET_SCRIPT=../../some_action.sh
test_init "ファイルが揃っている場合"
test_exec $TARGETSCRIPT
test_diff
test_init "ファイルが存在しない場合"
rm work/actual/aaa.txt
rm work/expected/aaa.txt
echo "ccc" > work/expected/ccc.txt
test_exec $TARGETSCRIPT
test_diff
_common.sh
# テストケース実行共通関数
test_init() {
local TITLE=$1
echo "$TITLE"
rm -rf work
mkdir -p work
cp -rp data/1-init work/actual
cp -rp data/2-expected work/expected
}
test_exec() {
pushd work/actual > /dev/null
echo "$@"
bash "$@"
popd > /dev/null
}
test_diff() {
echo "diff -r work/actual work/expected $*"
diff -r "work/actual" "work/expected" "$@"
RES="$?"
if [ $RES -eq 0 ]; then
echo "(OK. 差分なし)"
fi
echo
return $RES
}
個々のテクニックの具体的な説明
1. 実行に失敗したら即終了するように
ファイルの先頭で下記を書きます。
-e
は未定義の変数を参照したらエラーにし、
-u
はエラーがあったらその時点で中断するようにします。
set -eu
2. ファイル群の比較
実行結果と期待結果を比較する際に便利な関数を定義しておきます。
test_diff() {
# 実行内容を出力します
# $* は引数で与えられた内容を文字列的に取り出します
echo "diff -r work/actual work/expected $*"
# 比較します
# -r は階層的に比較します
# "$@" は引数で与えられた内容を展開して取り出します(空白区切りで別々の文字列として)
diff -r "work/actual" "work/expected" "$@"
# 差分有無を取り出します
RES="$?"
if [ $RES -eq 0 ]; then
echo "(OK. 差分なし)"
fi
# 差分がある場合はこの関数をエラーとしてシェルを終了させるために戻り値で成否を返します
return $RES
}
使い方
test_diff -x zzzz
- 新旧のディレクトリを引数で渡します。diff と同様のオプション引数が使えます。
たとえば-x
で除外するディレクトリを指定できます。
出力(OK)
diff -r work/actual work/expected -x zzzz
(OK. 差分なし)
出力(NG)
diff -r work/actual work/expected -x zzzz
diff -x zzzz -r work/actual/file1.txt work/expected/file1.txt
1,1c1,1
< aaa
---
> bbb
3. date の扱い
date で現在日時を扱っている場合、それを Mock するのは面倒です。
なので大元のスクリプトの方でも date の値は引数で渡すようにします。
テスト対象のスクリプトの冒頭
set -eu
CURRENT_YMD=${1:-第1引数にてYYYYMMDDで本日の日付を指定してください}
テスト実行
./script.sh "20231204"
本番実行
./script.sh "$(date +'%Y%m%d')"