Chuyển đến nội dung chính
友田 陽大
AWS Lambda in production
AWS
Lambda
テスト
サーバーレス
テスト容易性

Lambdaのテスト戦略:単体・結合・E2Eの設計、SDKモック、sam local、クラウドでの検証

AWS Lambdaを本番品質でテストする実装ガイド。AWS公式が定義する単体/結合/E2Eと『クラウドでのテストを優先せよ』という指針、薄いハンドラと純粋ロジックの単体テスト、aws-sdk-client-mock/motoによるSDKモック、sam localの使いどころと限界、使い捨てスタックによる結合・非同期の副作用検証まで、AWS公式仕様に忠実な実コードで解説します。

Published
Reading time
12 min read
Author
友田 陽大
Chia sẻ

「Lambda のテスト、ローカルでは全部通るのに、本番にデプロイすると権限エラーやイベント形状の違いで落ちる」——サーバーレスのテストで最も多い失望です。原因は明確で、Lambda のロジックの多くは『コード』ではなく『クラウドのサービス設定(IAM・イベントソース・クォータ)』に宿るから。手元のモックは、その肝心な部分を再現できません。

この記事は、AWS Lambda を本番品質でテストするための戦略ガイドです。AWS公式のテスト指針を起点に、薄いハンドラの単体テストSDKのモックsam local の使いどころと限界クラウドでの結合/E2Eまでを一気通貫で解説します。題材として、私が中核開発者として構築したサーバーレス決済プラットフォーム本番二重課金0件)を支えたテスト規律も交えます。Lambda 本体の実行モデルは姉妹記事 AWS Lambda 本番運用ガイド に委ね、本稿は**「どうテストするか」一点**に集中します。

この記事のルール:テストの定義・指針・ツール名は AWS 公式ドキュメント(Lambda testing guide ほか・2026年6月時点) と各ツールの公式ページに基づきます。ツールのバージョン・APIは改定されるため、本番導入前に必ず公式(末尾「参考」)で確認してください。


0. メンタルモデル:「クラウドでのテスト」を最優先にする

まず、AWS公式の最重要メッセージを受け取ります。これがサーバーレステストの設計を決めます。

  • 公式の指針:クラウドでのテストを最優先せよ。公式は明言します——「クラウドベースのテストが、関数とアプリケーションの品質を最も正確に測る」「セキュリティポリシー・サービス設定・クォータ・最新のAPIシグネチャまで包括的にテストできるのはクラウドだけ」。
  • モックの限界:「モックを使うテストは机上では通ってもクラウドで失敗しうる。結果が最新APIと一致しないことがあり、サービス設定やクォータはテストできない」。
  • エミュレータの限界:「エミュレータに依存するテストはローカルで成功してもクラウドで失敗しうる(本番のセキュリティポリシー・サービス間設定・クォータ超過のため)」「エミュレータは控えめに使え」。
  • だから比重が変わる:普通のアプリより結合テストの比重を上げる。なぜなら、ロジックの多くがサービス設定(IAM・イベントマッピング)に宿るから。「マネージドサービスそのものはテスト不要だが、それらとの結合はテストが必要」。

公式が定義する3層を、この順で設計します。

公式の定義
単体(unit)隔離されたコードブロックに対するテスト配送料計算のビジネスロジック検証
結合(integration)2つ以上のコンポーネント/サービスの相互作用(通常クラウド)関数がキューのイベントを処理することの検証
E2Eアプリ全体の振る舞いイベントが各サービス間を流れて注文が記録される

1. 単体テスト:薄いハンドラ+純粋ロジック

公式のテスト容易性の指針は一言です——「ハンドラはイベントを受け取り、ビジネスロジックに必要な詳細だけを渡す薄いアダプタにせよ」。こうすれば、Lambda 固有の事情を気にせず、ビジネスロジックを普通の単体テストで固められます。

// domain.ts — Lambdaを一切importしない純粋ロジック。最速・最安で大量のケースを回せる
export interface Order { id: string; amount: number; destination: "domestic" | "international"; }

export function deliveryFee(order: Order): number {
  if (order.amount < 0) throw new RangeError("amount must be non-negative"); // 境界で検証
  const base = order.destination === "international" ? 2000 : 500;
  return order.amount >= 10_000 ? 0 : base; // 1万円以上は送料無料
}
// handler.ts — 薄いアダプタ。eventからの抽出・検証だけを担い、判断はdomainに委ねる
import type { APIGatewayProxyEventV2 } from "aws-lambda";
import { deliveryFee, type Order } from "./domain";

export const handler = async (event: APIGatewayProxyEventV2) => {
  const body = JSON.parse(event.body ?? "{}") as Partial<Order>;
  if (!body.id || typeof body.amount !== "number" || !body.destination) {
    return { statusCode: 422, body: JSON.stringify({ message: "invalid order" }) };
  }
  return { statusCode: 200, body: JSON.stringify({ fee: deliveryFee(body as Order) }) };
};
// domain.test.ts — 純粋関数なのでクラウドもDockerも不要。境界値を厚くテストする
import { describe, it, expect } from "vitest";
import { deliveryFee } from "./domain";

describe("deliveryFee", () => {
  it("国内は500円、国際は2000円", () => {
    expect(deliveryFee({ id: "a", amount: 100, destination: "domestic" })).toBe(500);
    expect(deliveryFee({ id: "b", amount: 100, destination: "international" })).toBe(2000);
  });
  it("1万円以上は送料無料", () => {
    expect(deliveryFee({ id: "c", amount: 10_000, destination: "international" })).toBe(0);
  });
  it("負の金額は拒否する", () => {
    expect(() => deliveryFee({ id: "d", amount: -1, destination: "domestic" })).toThrow(RangeError);
  });
});

現実的なイベントで叩く:ハンドラ自体を単体テストするなら、sam local generate-event本物に近いサンプルイベント(API Gateway/S3/SQS等)を生成してくれます。手書きのイベントは形状がズレやすいので、生成物を基にします。


2. SDKのモック:依存を切って単体テストを速く保つ

ビジネスロジックの外側で、DynamoDB等のSDK呼び出しを含む薄い層を単体テストしたいことがあります。ここはSDKをモックします。本物のAWSに繋がないので速く、決定的です。

2.1 TypeScript:aws-sdk-client-mock

AWS SDK for JavaScript v3 のクライアントをモックする標準ライブラリが aws-sdk-client-mockAWS SDK for JavaScriptチーム推奨)。mockClient() で包み、.on(Command).resolves(...) で応答を定義します。

// repo.test.ts — DynamoDBDocumentClient をモックし、本物のAWSに繋がず単体テスト
import { mockClient } from "aws-sdk-client-mock";
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
import { describe, it, expect, beforeEach } from "vitest";
import { getOrder } from "./repo";

const ddbMock = mockClient(DynamoDBDocumentClient);
beforeEach(() => ddbMock.reset());

it("注文が見つかればドメインオブジェクトに変換して返す", async () => {
  ddbMock.on(GetCommand).resolves({ Item: { id: "o1", amount: 1200 } });
  await expect(getOrder("o1")).resolves.toEqual({ id: "o1", amount: 1200 });
});

it("見つからなければ null", async () => {
  ddbMock.on(GetCommand).resolves({ Item: undefined });
  await expect(getOrder("missing")).resolves.toBeNull();
});

2.2 Python:moto

Python では moto(公式の testing guide が名指しで紹介)。@mock_aws デコレータで boto3 呼び出しをインメモリにモックします。

# test_repo.py — moto で DynamoDB をモック(実リソース不要・インメモリ)
import boto3
from moto import mock_aws
from repo import get_order

@mock_aws
def test_get_order_returns_none_when_missing():
    ddb = boto3.resource("dynamodb", region_name="ap-northeast-1")
    ddb.create_table(
        TableName="orders",
        KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
        AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
        BillingMode="PAY_PER_REQUEST",
    )
    assert get_order("missing") is None

Powertoolsのテスト:構造化ログ/トレース/メトリクスを使う関数のテストでは、POWERTOOLS_DEV を立てるとLoggerは整形出力、Tracerは無効化、Metricsは標準出力への送出を無効化します。POWERTOOLS_SERVICE_NAME / POWERTOOLS_METRICS_NAMESPACE をテスト前に設定しておくと、メトリクスのスキーマ検証で落ちるのを防げます。

モックの注意:公式が言う通り、モックはIAM権限・クォータ・サービス設定を検証できない。単体テストは「ロジックの正しさ」専用と割り切り、設定の正しさはクラウドの結合テストで担保します(次章)。


3. ローカル検証:sam local の使いどころと限界

手元で素早く動かしたいときは AWS SAM のローカルコマンド。3つを役割で押さえます(いずれもDocker前提)。

コマンド何をするか既定ポート
sam local invoke関数を1回ローカル実行--event でイベント注入)
sam local start-apiローカルHTTPサーバでAPIとして叩く3000
sam local start-lambdaLambdaサービスを模したローカルエンドポイント(CLI/SDKから呼ぶ)3001
# 生成イベントでローカル1回実行 → 入出力を素早く確認
sam local generate-event apigateway http-api-proxy > events/http.json
sam local invoke OrdersFunction --event events/http.json

限界を正しく理解するのが本章の主眼です。公式は明言します——「ローカルテストは迅速な開発・デプロイ前確認には良いが、クラウド上のリソース間の権限などは検証できない可能な限りクラウドでテストせよ」。sam local で叩いて関数からAWSを呼ぶと、呼び出しIDがLambdaサービス由来ではなくなり、実行ロールを引き受けない——つまりIAMの検証にならない。だから sam local は「ロジックの素早い確認」に留め、本番品質はクラウドで担保します。


4. 結合・E2E:クラウドで、使い捨てスタックで

サーバーレスの品質は、結局クラウドで本物のサービスに対してテストしないと担保できません。公式の推奨パターンを実装に落とします。

4.1 ブランチ/PRごとに使い捨てスタックを立てる

公式は「ユニークな名前のリソースを各スタックに作る」「コードのブランチ境界でスタックを分ける」ことをベストプラクティスとします。CIでPRごとに一意プレフィックスのスタックをデプロイ→テスト→破棄します。

# 結合テストCI(抜粋):PRごとに使い捨てスタックを立て、本物のサービスでテストし、必ず壊す
jobs:
  integration:
    runs-on: ubuntu-latest
    permissions: { id-token: write, contents: read } # OIDCで鍵レス(デプロイ記事参照)
    env: { STACK: orders-pr-${{ github.event.number }} }
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v6
        with: { role-to-assume: arn:aws:iam::123456789012:role/ci-deploy, aws-region: ap-northeast-1 }
      - uses: aws-actions/setup-sam@v2
      - run: sam build && sam deploy --stack-name "$STACK" --no-confirm-changeset --resolve-s3
      - run: npm run test:integration          # 本物のAPI/DB/キューに対して検証
      - if: always()                            # 成否に関わらずスタックを破棄(コスト/汚染防止)
        run: sam delete --stack-name "$STACK" --no-prompts

4.2 非同期は「副作用」をポーリングで検証する

イベント駆動(SQS/EventBridge/Streams)は戻り値が無いので、下流の副作用を観測してアサートします。公式のArrange-Act-Assert:関数を起動(Act)→宛先(SQS/DynamoDB)から結果を取得して検証(Assert)

// 非同期フローの結合テスト:イベント投入 → 下流の副作用が現れるまでポーリングして検証
import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";

async function waitForItem(table: string, id: string, timeoutMs = 30_000) {
  const ddb = new DynamoDBClient({});
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const { Item } = await ddb.send(new GetItemCommand({ TableName: table, Key: { id: { S: id } } }));
    if (Item) return Item;                        // 副作用が現れた=関数が正しく処理した
    await new Promise((r) => setTimeout(r, 1000)); // 1秒間隔でポーリング
  }
  throw new Error(`item ${id} did not appear within ${timeoutMs}ms`); // 失敗を明確に
}

E2Eはモックを使わない(公式)。API Gateway→Lambda→DynamoDB を通しで叩き、各サービスの設定とIAM権限まで含めて検証します。これが「ローカルで通って本番で落ちる」を構造的に潰す唯一の方法です。


5. サーバーレスのテストピラミッド:比重を組み替える

普通のアプリの「単体多め・E2E少なめ」を、サーバーレスでは結合に厚く組み替えます(公式が「結合テストに注力せよ」と明言)。

        ▲  E2E(少数・モック禁止)
       ╱ ╲   主要なユーザーフローを通しで。設定・IAM・サービス間連携を検証
      ╱   ╲
     ╱ 結合 ╲  ← サーバーレスはここが厚い
    ╱  多め  ╲   関数×実サービス(DB/キュー/API GW)。設定の正しさはここでしか分からない
   ╱─────────╲
  ╱   単体     ╲  最多・最速・最安。純粋ロジック+SDKモック。IAMやクォータは検証しない
 ╱─────────────╲

設計指針:

  • 単体:ビジネスロジック(純粋関数)と、SDKモックでの薄いデータ層。速く・大量に・境界値を厚く
  • 結合:関数を実サービスに繋いで、設定・権限・イベント形状を検証。サーバーレスではここが品質の主戦場
  • E2E:主要フローを通しで、モック禁止。数は絞る。
  • ローカル(sam local):ピラミッドの「層」ではなく、開発中の素早いフィードバック手段。品質保証の代替にはしない。

6. まとめ:Lambdaテスト戦略チートシート

  • 最優先はクラウドでのテスト(公式)。モック/エミュレータはIAM・クォータ・設定・最新API差分を再現できない。「机上で通って本番で落ちる」の正体。
  • 単体:ハンドラは薄いアダプタ、ビジネスロジックは純粋関数に切り出して厚くテスト。sam local generate-event で現実的なイベントを使う。
  • SDKモック:TypeScript=aws-sdk-client-mock(AWS SDK for JSチーム推奨)、Python=moto@mock_aws・公式紹介)。ロジック専用、設定は検証しない。
  • sam localinvoke/start-api(3000)/start-lambda(3001)はDocker前提。権限は検証できないので控えめに。
  • 結合/E2Eブランチ/PRごとの使い捨てスタックを立て→実サービスで検証→必ず破棄。非同期は副作用をポーリング。E2Eはモック禁止。
  • ピラミッド:サーバーレスは結合に厚く。設定とIAMはそこでしか守れない。

私は決済プラットフォームで、本番二重課金0件を「祈り」ではなくテストで担保しました。冪等化ロジックを純粋関数で厚く単体テストし、SQS→Lambda→DynamoDBの冪等な副作用を使い捨てスタックの結合テストで検証し、E2Eで決済フローを通しで守る——この層構造が、止められない決済基盤を安心して変更し続ける土台です。テストを含むAI駆動開発の品質ゲート設計は 品質ゲートの作り方 も参照ください。

「自社のサーバーレスを、机上ではなく本番で確実に動く形でテストしたい」——テストピラミッドの設計から使い捨てスタックのCI、非同期の副作用検証まで、一人 × 生成AI(Claude Code)の速さで伴走します。 現状のテスト戦略の診断からでも、お気軽にご相談ください。


参考(公式ドキュメント)

友田

友田 陽大

Người phát triển sản phẩm đoạt Giải thưởng Bộ trưởng (METI). Với TypeScript + Python + AWS, tôi một mình triển khai trọn gói SaaS, chuyển đổi số công nghiệp và AI tạo sinh (RAG) sẵn sàng cho production — từ phân tích yêu cầu đến hạ tầng và vận hành.

Got a challenge?

From design to implementation and operations — solo × generative AI

Implementation like this article's, end to end from requirements to production. Start with a free 30-minute technical consult and tell me about your situation.

Nhận cả dự án trọn gói lẫn vai trò cố vấn kỹ thuật. Hãy bắt đầu bằng buổi tư vấn miễn phí 30 phút.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Cũng đáng đọc