Bashテスト は何をしますか?
シェルスクリプト向けのテストフレームワークとパターン。shunit2とshellspecに焦点を当てています。
フレームワークの選択
| フレームワーク | 強み | 最適な用途 |
|---|---|---|
| shunit2 | xUnit形式、シンプル、ポータブル | 単体テスト、関数テスト |
| shellspec | BDD形式、モダン、高機能 | 振る舞いスペック、本格的なプロジェクト |
shunit2
インストール
# 直接ダウンロード
curl -L https://github.com/kward/shunit2/raw/master/shunit2 -o shunit2
# またはパッケージマネージャー経由
apt install shunit2
brew install shunit2
基本的なテスト構造
#!/usr/bin/env bash
# テスト対象のスクリプトを読み込む
source ./my_script.sh
# テスト関数はtestで始まる
test_addition() {
result=$(add 2 3)
assertEquals "2 + 3 は 5 になるはず" "5" "$result"
}
test_string_output() {
result=$(greet "World")
assertEquals "Hello, World" "$result"
}
test_file_creation() {
create_temp_file
assertTrue "テンポラリファイルが存在するはず" "[ -f /tmp/testfile ]"
}
test_exit_code() {
validate_input "valid"
assertEquals "有効な入力に対して0を返すはず" 0 $?
}
# セットアップとティアダウン
oneTimeSetUp() {
# 全テストの前に一度だけ実行
export TEST_DIR=$(mktemp -d)
}
oneTimeTearDown() {
# 全テストの後に一度だけ実行
rm -rf "$TEST_DIR"
}
setUp() {
# 各テストの前に実行
cd "$TEST_DIR"
}
tearDown() {
# 各テストの後に実行
rm -f "$TEST_DIR"/*
}
# shunit2を読み込む
source shunit2
shunit2 アサーション
# 等価
assertEquals [メッセージ] 期待値 実際の値
assertNotEquals [メッセージ] 予期しない値 実際の値
# Null/空
assertNull [メッセージ] 値
assertNotNull [メッセージ] 値
# 真偽/ステータス
assertTrue [メッセージ] 条件
assertFalse [メッセージ] 条件
# 同一参照(文字列比較)
assertSame [メッセージ] 期待値 実際の値
assertNotSame [メッセージ] 予期しない値 実際の値
# 包含(文字列内)
assertContains [メッセージ] コンテナ 内容
# 終了ステータス
assertEquals 0 $?
関数の分離テスト
#!/usr/bin/env bash
# test_my_functions.sh
source ./my_functions.sh
test_calculate_sum_empty() {
result=$(calculate_sum)
assertEquals "空の合計は0になるはず" "0" "$result"
}
test_calculate_sum_single() {
result=$(calculate_sum 5)
assertEquals "5" "$result"
}
test_calculate_sum_multiple() {
result=$(calculate_sum 1 2 3 4 5)
assertEquals "15" "$result"
}
test_validate_email_valid() {
validate_email "[email protected]"
assertTrue "有効なメールはパスするはず" $?
}
test_validate_email_invalid() {
validate_email "not-an-email"
assertFalse "無効なメールは失敗するはず" $?
}
source shunit2
shellspec
インストール
# curl経由
curl -fsSL https://git.io/shellspec | sh
# Homebrew経由
brew install shellspec
# パッケージマネージャー経由
apt install shellspec
ディレクトリ構造
project/
├── lib/
│ └── functions.sh
├── spec/
│ ├── spec_helper.sh
│ ├── functions_spec.sh
│ └── support/
│ └── fixtures/
└── .shellspec
設定(.shellspec)
--require spec_helper
--format documentation
--color
基本的なスペック構造
shellspec マッチャー
# 出力マッチャー
The output should eq "完全一致"
The output should include "部分一致"
The output should start with "プレフィックス"
The output should end with "サフィックス"
The output should match pattern "*glob*"
The output should be blank
# ステータスマッチャー
The status should be success # 終了コード0
The status should be failure # 終了コード非ゼロ
The status should eq 1 # 特定のコード
# 変数マッチャー
The variable VAR should eq "値"
The variable VAR should be defined
The variable VAR should be undefined
# ファイルマッチャー
The file "パス" should be exist
The file "パス" should be file
The file "パス" should be directory
The file "パス" should be readable
# パスマッチャー
The path "file.txt" should be exist
shellspecでのモック
Describe 'デプロイ関数'
# 外部コマンドをモック
curl() {
echo "モックされた応答"
return 0
}
It 'APIエンドポイントを呼び出す'
When call deploy "server"
The output should include "モックされた応答"
End
End
Describe 'ファイル操作'
# 関数オーバーライドでモック
Mock rm
echo "rm が次の引数で呼び出されました: $*"
End
It 'ファイル削除を試みる'
When call cleanup_temp
The output should include "rm が次の引数で呼び出されました"
End
End
スペックヘルパー
# spec/spec_helper.sh
# 共通関数の読み込み
spec_helper_precheck() {
minimum_version "0.28.0"
}
spec_helper_loaded() {
# テスト環境のセットアップ
export TEST_MODE=true
}
spec_helper_configure() {
# プロジェクトの関数をインポート
import 'lib/functions.sh'
}
テストパターン
終了コードのテスト
# shunit2
test_success_exit() {
run_command "valid_input"
assertEquals 0 $?
}
test_error_exit() {
run_command "invalid_input"
assertEquals 1 $?
}
# shellspec
It '成功時に0で終了する'
When call run_command "valid_input"
The status should eq 0
End
標準出力と標準エラー出力のテスト
# shunit2
test_stdout() {
result=$(my_function 2>/dev/null)
assertEquals "期待される出力" "$result"
}
test_stderr() {
error=$(my_function 2>&1 >/dev/null)
assertContains "$error" "エラーメッセージ"
}
# shellspec
It '標準出力に出力する'
When call my_function
The stdout should eq "期待される出力"
End
It 'エラーを標準エラー出力に出力する'
When call my_function
The stderr should include "エラーメッセージ"
End
フィクスチャを使ったテスト
# テストフィクスチャのセットアップ
setUp() {
TEST_DIR=$(mktemp -d)
cat > "$TEST_DIR/config.json" <<EOF
{
"key": "value"
}
EOF
}
tearDown() {
rm -rf "$TEST_DIR"
}
test_config_parsing() {
result=$(parse_config "$TEST_DIR/config.json")
assertEquals "value" "$result"
}
対話的な関数のテスト
# ヒアドキュメントで入力を提供
test_interactive() {
result=$(my_prompt <<EOF
yes
EOF
)
assertEquals "confirmed" "$result"
}
# またはprintfを使用
test_with_input() {
result=$(printf 'yes\n' | my_prompt)
assertEquals "confirmed" "$result"
}
テストの実行
# shunit2
./test_script.sh
# shellspec
shellspec # すべてのスペックを実行
shellspec spec/file_spec.sh # 特定のスペックを実行
shellspec --format tap # TAP形式で出力
shellspec --jobs 4 # 並列実行
ベストプラクティス
- 可能な限りテストごとに1つのアサーション
- 説明的なテスト名 - 何をテストしているかを説明
- テストの分離 - テスト間の依存関係なし
- エッジケースのテスト - 空の入力、特殊文字、大きなデータ
- tearDownでリソースをクリーンアップ
- 外部コマンドをモック - curlをテストするのではなく、自身のロジックをテスト
- 出力だけでなく終了コードもテスト