スモークテスト

デプロイ後にシステムの基本機能が動作することを確認する簡易テスト

テストCI/CD

スモークテストとは

スモークテスト (Smoke Test) は、デプロイ直後にシステムの基本機能が動作することを確認する簡易テストである。名前の由来は電子回路の検査だとされる。新しい基板を差して電源を入れ、煙が出たらそこで打ち切る、それ以上テストする意味はない、という話だ (テスト技術者の書籍『Lessons Learned in Software Testing』がこの由来を挙げている)。

全機能を網羅的にテストするのではなく、「サイトが表示されるか」「API が応答するか」「DB に接続できるか」といった最低限の動作確認に焦点を当てる。

スモークテストの位置づけ

スモークテストは合否を返すだけの検査ではなく、ロールバックの引き金だ。判定を人間の目視に任せると引き金が引かれないので、失敗が自動的にロールバックへつながる位置に置く。

デプロイ → スモークテスト → (成功) → 本番トラフィック投入
                         → (失敗) → 即座にロールバック
テスト種類タイミング範囲速度
単体テストビルド関数・クラスミリ秒
結合テストビルド時モジュール間
E2E テスト検証環境へのデプロイ後ユーザーフロー
スモークテスト本番デプロイ直後基本機能の動作確認秒〜分

E2E テストとの違いは範囲の広さではなく目的だ。E2E テストは仕様どおりに動くかを確かめる。スモークテストは、いま本番に載っている実物が動いているかを確かめる。相手が本番環境なので、書き込みを伴う経路は原則として叩けない。ここが「E2E テストの一部を流用すれば済む」とはいかない理由になる。

Lambda + API Gateway でのスモークテスト

見落としやすいのは、応答が 200 でも中身が旧バージョンのことがある点だ。デプロイが片方だけ終わっている状態や、手前のキャッシュが古い応答を返している状態でも 200 は返る。ヘルスチェックにビルドの識別子を載せ、期待するバージョンと一致するかまで見る。

import assert from 'node:assert/strict';

// デプロイ後に実行するスモークテスト
// expectedVersion = 今デプロイしたコミット SHA など
async function smokeTest(baseUrl: string, expectedVersion: string): Promise<void> {
  // 1. ヘルスチェック: 200 だけでなく、載っている版まで確認する
  const health = await fetch(`${baseUrl}/health`);
  assert.equal(health.status, 200, 'Health check failed');
  const body = (await health.json()) as { version?: string };
  assert.equal(body.version, expectedVersion, `Old version is still served: ${body.version}`);

  // 2. 主要 API エンドポイント (読み取りのみ)
  const users = await fetch(`${baseUrl}/api/users?limit=1`);
  assert.equal(users.status, 200, 'Users API failed');

  // 3. 静的アセットの配信 (キャッシュを迂回して取る)
  const index = await fetch(baseUrl, { cache: 'no-store' });
  assert.equal(index.status, 200, 'Index page failed');
  assert.ok((await index.text()).includes('<!DOCTYPE html>'), 'Invalid HTML');
}

CloudFront + S3 でのスモークテスト

CloudFront を挟む構成では、キャッシュ無効化が終わる前に叩くと旧ファイルが返り、テストは通ってしまう。無効化の完了を待ってから検査する。

# deploy.sh の最後にスモークテストを実行
SITE_URL=$(aws cloudformation describe-stacks --stack-name myapp-dev \
  --no-paginate --query "Stacks[0].Outputs[?OutputKey=='SiteUrl'].OutputValue" \
  --output text | head -n 1)

# キャッシュ無効化を出し、完了を待つ (待たずに叩くと旧ファイルで合格してしまう)
# DIST_ID は配信の ID。デプロイ工程から引き継ぐ
INVALIDATION_ID=$(aws cloudfront create-invalidation \
  --distribution-id "$DIST_ID" --paths "/*" \
  --query 'Invalidation.Id' --output text)
aws cloudfront wait invalidation-completed \
  --distribution-id "$DIST_ID" --id "$INVALIDATION_ID"

# HTML が返るか確認
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$SITE_URL")
if [ "$STATUS" != "200" ]; then
  echo "❌ Smoke test failed: HTTP $STATUS"
  exit 1
fi

# sitemap.xml が正しいドメインを含むか確認
curl -s "$SITE_URL/sitemap.xml" | grep -q "example.com" || {
  echo "❌ Sitemap contains wrong domain"
  exit 1
}

echo "✅ Smoke tests passed"

スモークテストの設計原則

  • 高速に実行できること (30 秒以内が目安)
  • 失敗時に即座にロールバックできること
  • 外部依存 (DB、API) への接続を確認すること
  • 本番環境のデータを変更しないこと (読み取りのみ)
  • 今デプロイした版が返っていることまで確認すること (200 だけでは足りない)

カナリアリリースとの組み合わせ

スモークテストをカナリアリリースと組み合わせると、新バージョンに少量のトラフィックを流し、スモークテストが通ったら段階的にトラフィックを増やす。失敗したら自動ロールバックする。

ここで注意が要る。加重ルーティングの入口を叩くと、リクエストは確率的に旧バージョンへ振り分けられる。新バージョンを検査したつもりで旧バージョンを検査してしまう。カナリアに当てるには、新バージョンだけを指す入口 (Lambda なら新バージョンを指す別エイリアス、ロードバランサーならヘッダー条件のリスナールール) を用意して、そこへ向けて実行する。前掲のバージョン確認は、この取り違えにも効く。

スモークテストは書いた直後が一番よく効き、放っておくと形骸化する。ヘルスチェックが常に固定値を返すだけになっていたり、失敗しても後続の工程が続いてしまう配線になっていたりするからだ。意図的に壊したビルドをたまに流し、ちゃんと落ちてロールバックまで走るかを確かめておく。

この記事は役に立ちましたか?

関連用語

関連する記事