生成AIにスクリプトを書いてもらうとき、「書いて」で終わらず、期待どおりに動くかの検証まで依頼すると、成果物が変わります。
今回は、指定したEC2を1台停止するスクリプトを、AWSへ接続せずにテストします。
停止要求と停止完了を分ける
処理は次の3段階です。
停止を要求する → 停止完了を確認する → 成功を表示する
AWS CLIには、停止を要求するstop-instancesと、停止状態になったかを15秒ごとに確認するwait instance-stoppedがあります(40回、約10分で確認できなければ失敗します)。停止要求・停止完了の確認
停止要求が通っただけで、停止完了と判断しないようにします。
次をstop-ec2.shとして保存します。
#!/usr/bin/env bash
set -euo pipefail
export AWS_PAGER=""
export AWS_CLI_AUTO_PROMPT=off
if [[ $# -ne 2 || -z "$1" || -z "$2" ]]; then
echo "Usage: $0 REGION INSTANCE_ID" >&2
exit 2
fi
region=$1
instance_id=$2
if ! aws ec2 stop-instances \
--region "$region" --instance-ids "$instance_id" >/dev/null; then
echo "Stop request failed: $instance_id" >&2
exit 1
fi
if ! aws ec2 wait instance-stopped \
--region "$region" --instance-ids "$instance_id"; then
echo "Could not confirm stopped state: $instance_id" >&2
exit 1
fi
echo "Stopped: $instance_id"
偽物のawsコマンドに差し替える
実AWSで都合よく失敗を起こす必要はありません。
一時ディレクトリにawsという実行ファイルを作り、そのディレクトリをPATHの先頭に追加します。検証対象のスクリプトは変更せず、外部コマンドだけを差し替えます。
偽物は1本です。環境変数TEST_CASEで動きを切り替えます。
| TEST_CASE | 偽物の動作 |
|---|---|
success |
停止要求・完了確認とも成功 |
stop-failure |
停止要求が失敗 |
wait-failure |
停止要求は成功し、完了確認が失敗 |
さらに、呼ばれたコマンドをファイルへ記録します。これで「停止要求が失敗したら、完了確認を呼ばない」ことや、「引数が足りなければ、awsを一度も呼ばない」ことも検証できます。
テストを実行する
次を、同じディレクトリのtest-stop-ec2.shとして保存します。
#!/usr/bin/env bash
set -euo pipefail
script_dir=$(cd "$(dirname "$0")" && pwd)
test_dir=$(mktemp -d)
trap 'rm -rf "$test_dir"' EXIT
fake_bin="$test_dir/bin"
mkdir "$fake_bin"
# 偽物のawsを作る
cat > "$fake_bin/aws" <<'FAKE'
#!/usr/bin/env bash
set -eu
printf '%s\n' "$*" >> "$AWS_CALL_LOG"
case "$1 $2" in
"ec2 stop-instances")
[[ "$TEST_CASE" != "stop-failure" ]]
;;
"ec2 wait")
[[ "$3" == "instance-stopped" ]]
[[ "$TEST_CASE" != "wait-failure" ]]
;;
*)
echo "Unexpected command: $*" >&2
exit 99
;;
esac
FAKE
chmod +x "$fake_bin/aws"
# ケースごとに終了コード・出力・呼び出し記録を取得する
run_case() {
local name=$1 expected_status=$2 status=0
TEST_CASE="$name" \
AWS_CALL_LOG="$test_dir/$name.log" \
PATH="$fake_bin:$PATH" \
bash "$script_dir/stop-ec2.sh" \
ap-northeast-1 i-0123456789abcdef0 \
> "$test_dir/$name.out" 2> "$test_dir/$name.err" || status=$?
test "$status" -eq "$expected_status"
}
# 期待する呼び出しと出力を用意する
printf '%s\n' \
'ec2 stop-instances --region ap-northeast-1 --instance-ids i-0123456789abcdef0' \
> "$test_dir/stop-only.expected"
cat "$test_dir/stop-only.expected" > "$test_dir/both.expected"
printf '%s\n' \
'ec2 wait instance-stopped --region ap-northeast-1 --instance-ids i-0123456789abcdef0' \
>> "$test_dir/both.expected"
printf 'Stopped: i-0123456789abcdef0\n' > "$test_dir/success.expected"
# 正常系
run_case success 0
diff -u "$test_dir/success.expected" "$test_dir/success.out"
test ! -s "$test_dir/success.err"
diff -u "$test_dir/both.expected" "$test_dir/success.log"
echo 'PASS: success'
# 停止要求が失敗したら、完了確認を呼ばない
run_case stop-failure 1
test ! -s "$test_dir/stop-failure.out"
grep -Fq 'Stop request failed: i-0123456789abcdef0' "$test_dir/stop-failure.err"
diff -u "$test_dir/stop-only.expected" "$test_dir/stop-failure.log"
echo 'PASS: stop-failure'
# 完了確認が失敗したら、成功を表示しない
run_case wait-failure 1
test ! -s "$test_dir/wait-failure.out"
grep -Fq 'Could not confirm stopped state: i-0123456789abcdef0' "$test_dir/wait-failure.err"
diff -u "$test_dir/both.expected" "$test_dir/wait-failure.log"
echo 'PASS: wait-failure'
# 引数が足りなければ、awsを呼ばない
status=0
AWS_CALL_LOG="$test_dir/usage.log" \
PATH="$fake_bin:$PATH" \
bash "$script_dir/stop-ec2.sh" ap-northeast-1 \
> "$test_dir/usage.out" 2> "$test_dir/usage.err" || status=$?
test "$status" -eq 2
grep -Fq 'Usage:' "$test_dir/usage.err"
test ! -e "$test_dir/usage.log"
echo 'PASS: usage'
実行します。
bash test-stop-ec2.sh
成功すると次が表示されます。
PASS: success
PASS: stop-failure
PASS: wait-failure
PASS: usage
確認しているのは、終了コード・標準出力・標準エラー・呼び出したコマンドと順序です。
testやdiffが期待との違いを検出すると、テストスクリプトは失敗終了します。一時ディレクトリは実行ごとに作るので、前回のログが混ざりません。終了時にはtrapで削除します。
AIには、壊した場合も確認させる
今回のコードは、上記の4ケースが通ることを実行して確認しました。
さらに、検証対象を一時的に変更しました。
| 意図的に加えた変更 | 結果 |
|---|---|
| 停止完了の確認を削除 | テストが失敗 |
| 完了確認の失敗を無視 | テストが失敗 |
| 停止対象を別のIDに変更 | テストが失敗 |
| 引数チェックを削除 | テストが失敗 |
正しいコードで通り、仕様を壊すと落ちる。 テスト自体も、このように確かめられます。
AIへの依頼も具体的にします。
偽物のawsコマンドをPATHの先頭に置き、スクリプトを実行して検証してください。正常系・停止要求の失敗・完了確認の失敗・引数不足を再現し、終了コード、出力、呼び出し記録を照合してください。意図的に壊した場合にテストが落ちることと、未確認の範囲も報告してください。
検証した範囲を明確にする
このテストで確認したのは、スクリプトの分岐・引数・出力です。本物のAWS CLIは呼ばず、EC2も停止していません。
認証、IAM権限、AWS CLI自体の待機動作は、停止してよい検証用インスタンスで別途確認します。また、完了確認の失敗は「停止状態を確認できなかった」という意味であり、「EC2が必ず稼働中」という意味ではありません。
AIにはコードだけでなく、実行したテスト、結果、未確認の範囲まで成果物にしてもらう。短いスクリプトでも、そこまで依頼する価値があります。

