AWS Emulatorで通ったテストは本番でも通る?公開スコア4万件で見たサービス別の一致率と、自分のコードで確かめる方法

当ページのリンクには広告が含まれています。

AWSが11月11日に無償公開したAWS Emulatorは、S3やDynamoDBなどのAPIを手元のPCで再現します。AWSアカウントなしで動き、Claude Codeのようなコーディングエージェントに「書く→テストする→直す」を数ミリ秒の応答で繰り返させる使い方が想定されています。

任せる前に知っておきたいのは、エミュレーターで通ったテストが本番のAWSでも通るのかどうかです。AWSはリリースごとに、実際のAWSとエミュレーターの挙動を比べた試験結果を公開しています。2026年11月11日版の4万2,937件を、サービス別、さらに「AWSが成功させる操作」と「AWSが拒否する操作」に分けて数えました。

結果は、サービスによって大きく違いました。SQSとKMSは9割以上が本物と一致します。一方、Lambdaで関数を実際に動かす試験の一致は約3割でした。AWSなら拒否される不正なバケットポリシーを、エミュレーターが受け付けた記録もあります。以下では、その数字と、自分のコードが使う操作で同じ集計をするスクリプトを示します。

目次

AWS Emulatorのサービス別一致率:SQS・KMSは9割超、Lambda・SSMは半分以下

AWSは、エミュレーターと実際のAWSの挙動を比べる短いテストを評価スイートとして公開しています。各テストは、呼出しの結果・作られたリソース・エラーの種類まで、AWSと同じなら「通過」です。採点結果はリリースに添付されたscores.json.gzに入っています。

評価スイートの一覧(manifest.jsonl)には、各テストが「AWSが成功させる操作」を確かめるのか、「AWSが拒否する操作」を確かめるのかを示すラベルがあります。このラベルで採点結果を分けて数えました。対象はv1.0.322の採点(2026年11月11日0時9分UTC)で、一覧と照合できた2万9,255件のうち、各サービスを主な対象とするテストです。

  • SQS:成功させる操作 97%(147/151)、拒否する操作 91%(135/149)
  • KMS:成功させる操作 97%(116/120)、拒否する操作 97%(114/118)
  • S3:成功させる操作 91%(212/234)、拒否する操作 77%(180/235)
  • DynamoDB:成功させる操作 89%(260/291)、拒否する操作 69%(228/330)
  • Step Functions:成功させる操作 85%(235/275)、拒否する操作 59%(142/242)
  • IAM:成功させる操作 84%(219/261)、拒否する操作 68%(126/185)
  • API Gateway:成功させる操作 78%(236/304)、拒否する操作 72%(196/274)
  • Lambda:成功させる操作 45%(165/367)、拒否する操作 46%(98/212)
  • Bedrock Runtime:成功させる操作 27%(12/45)、拒否する操作 42%(17/40)
  • Systems Manager:成功させる操作 28%(39/139)、拒否する操作 23%(29/125)

かっこ内は「通過したテスト数/テスト数」です。今回集計した16サービス全体では、成功する操作の試験が74%、拒否される操作の試験が67%通過しました。拒否される操作のほうが一致しにくい傾向は、S3(91%→77%)、DynamoDB(89%→69%)、Step Functions(85%→59%)でもはっきり出ています。

正常な使い方を中心に書いたテストは、SQS・KMS・S3・DynamoDBなら、エミュレーターの結果をかなり信用できます。エラー処理や入力チェックのテストでは、同じ4サービスでも信用できる度合いが一段下がります。

Lambdaは「関数を作る」は通るが「関数を動かす」は3割

AWSの説明ページは、リソースを作る・設定する系のAPIで最も本物に近く、データを読み書きする系のAPIはサービスによって異なると書いています。評価スイートのラベルでこの2つを分けると、差が最も大きかったのはLambdaでした。

  • 関数の作成・設定:187件中144件が通過(77%)
  • 関数の実行・呼出し:429件中130件が通過(30%)

実行系の失敗には、Pythonのasyncio.runが完了するか、レイヤーに入れたPythonパッケージを読み込めるか、構造化ログの項目がそろうか、といったテストが並びます。同じPythonでも、イベントがJSONとして渡るか、例外の型名が返るかといったテストは通っていました。関数の中身の動きをエミュレーターで確かめるのは、まだ難しい状態です。

反対に、DynamoDBは設定系が73%、読み書き系が80%で、データ操作のほうが一致していました。「設定系なら安心」と一律には言えないため、サービスごとに数字を見る必要があります。

AWSなら拒否される呼出しが、エミュレーターでは成功する

採点結果には、呼出しごとに期待したステータスと実際のステータスを残した記録もあります。この種類の記録はAWSの文書で説明されていませんが、中身は「AWSの応答」と「エミュレーターの応答」の対です。そこから、AWSはエラーを返したのにエミュレーターが成功(2xx)を返した呼出しを数えました。

  • S3:16件。例えば、書式の誤ったバケットポリシーの設定(PutBucketPolicy)で、AWSは400 MalformedPolicy、エミュレーターは204(成功)
  • IAM:3件。誤ったポリシー文書でのCreatePolicy・CreateRole・PutUserPolicyを、AWSは400 MalformedPolicyDocumentで拒否、エミュレーターは200
  • Lambda:12件、ECS:8件、Systems Manager:6件、RDS:5件

このずれは、エージェントにテストを任せたときに問題になります。エージェントが書いたポリシーに誤りがあっても、エミュレーターでは成功が返り、エージェントは「テストが通った」と報告します。誤りに気づくのは、AWSへデプロイした後です。

これとは別に、エミュレーターはIAMポリシーやリソースベースのポリシーをAWSと同じには評価しません。AWSの説明ページは、セキュリティの試験には使わないよう明記しています。ポリシーの書式も、権限があるかどうかの判定も、実際のAWSで確かめる部分として残すのが安全です。

自分のコードが使う操作で一致率を数えるスクリプト

AWSは、自分のプロジェクトが動くかを確かめる手順として、使う操作を書き出し、評価スイートの一覧で該当テストを探し、リリースの採点結果と照らす方法を案内しています。コーディングエージェント用のスキルに任せる方法もあります。下のスクリプトは、手で照らす部分を自動にしたものです。Python 3.9以降の標準ライブラリだけで動きます。

まず採点結果と一覧を同じフォルダーに保存します。採点結果は、使っているエミュレーターと同じ版のものを選びます。

curl -LO https://github.com/aws/aws-emulator/releases/download/v1.0.322/scores.json.gz
curl -LO https://raw.githubusercontent.com/aws/aws-emulator-evaluation-suite/main/manifest.jsonl

次のスクリプトをemu_fidelity.pyとして保存します。

"""AWS Emulatorの公開スコアを、サービス別・成功系/拒否系に分けて集計する(標準ライブラリのみ)。

使い方: python3 emu_fidelity.py scores.json.gz manifest.jsonl [サービス ...] [--ops 操作,操作]
  scores.json.gz  github.com/aws/aws-emulator のリリース添付ファイル
  manifest.jsonl  github.com/aws/aws-emulator-evaluation-suite の同名ファイル
  サービス        manifestのservice名(s3, dynamodb, lambda など)。省略時は主要サービス
  --ops           自分のコードが呼ぶ操作(例 s3:PutObject,sqs:SendMessage)。含むテストだけ数える
"""
import argparse
import collections
import gzip
import json

DEFAULT_SERVICES = ["s3", "dynamodb", "lambda", "sqs", "sns", "stepfunctions", "events", "apigateway",
                    "iam", "kms", "secretsmanager", "ssm", "cloudformation", "ecs", "rds", "bedrock-runtime"]
PLANES = ["plane:control", "plane:data"]
COLUMNS = {"axis:happy-path": "成功系", "axis:rejection": "拒否系",
           "plane:control": "設定系", "plane:data": "利用系"}


def load(scores_path, manifest_path):
    scores = json.load(gzip.open(scores_path))
    manifest = {}
    for line in open(manifest_path, encoding="utf-8"):
        row = json.loads(line)
        manifest[row["check_id"]] = row
    return scores, manifest


def columns_of(row):
    return [label for label in row["labels"] if label in COLUMNS]


def rate(counter):
    judged = counter["pass"] + counter["fail"]
    return f"{counter['pass']}/{judged} ({counter['pass'] / judged:.0%})" if judged else "-"


def behavioral_table(scores, manifest, services, ops):
    table = collections.defaultdict(lambda: collections.defaultdict(collections.Counter))
    unmatched = 0
    for unit in scores["units"]:
        if unit["kind"] != "behavioral":
            continue
        row = manifest.get(unit["detail"]["checkId"])
        if row is None:
            unmatched += 1
            continue
        if row["service"] not in services:
            continue
        if ops and not ops.intersection(row["operations"]):
            continue
        for column in columns_of(row):
            table[row["service"]][column][unit["outcome"]] += 1
    return table, unmatched


def accepted_rejections(scores, services):
    """wireテストで、AWSは4xx/5xxで拒否したのにエミュレーターが2xxで受け付けた件数。"""
    counts = collections.Counter()
    examples = collections.defaultdict(list)
    for unit in scores["units"]:
        detail = unit["detail"]
        if unit["kind"] != "wire" or detail.get("mismatch") != "status":
            continue
        expected, actual = detail["expected"]["status"], detail["actual"]["status"]
        if expected < 400 or actual >= 300:
            continue
        for service in unit["services"]:
            name = service.split("#")[0].removeprefix("com.amazonaws.")
            if name in services:
                counts[name] += 1
                examples[name].append(f"{detail['operation']}(AWS {expected} {detail['expected'].get('errorCode', '')}→"
                                      f"エミュ {actual})")
    return counts, examples


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("scores")
    parser.add_argument("manifest")
    parser.add_argument("services", nargs="*")
    parser.add_argument("--ops", default="")
    args = parser.parse_args()
    services = args.services or DEFAULT_SERVICES
    ops = {op.strip() for op in args.ops.split(",") if op.strip()}
    scores, manifest = load(args.scores, args.manifest)
    print(f"スコア: commit {scores['commit'][:12]} / 採点 {scores['scoredAt']} / テスト {len(scores['units'])}件")

    table, unmatched = behavioral_table(scores, manifest, set(services), ops)
    print(f"behavioralテストのうちmanifestと照合できなかった件数: {unmatched}")
    print("サービス | " + " | ".join(COLUMNS.values()) + " | 判定外(skipped)")
    total = collections.defaultdict(collections.Counter)
    for service in services:
        axes = table.get(service, {})
        skipped = sum(axes.get(plane, collections.Counter())["skipped"] for plane in PLANES)
        print(f"{service} | " + " | ".join(rate(axes.get(c, collections.Counter())) for c in COLUMNS) + f" | {skipped}")
        for axis, counter in axes.items():
            total[axis].update(counter)
    print("合計 | " + " | ".join(rate(total.get(c, collections.Counter())) for c in COLUMNS))

    counts, examples = accepted_rejections(scores, set(services))
    print("wireテスト: AWSが拒否した呼出しをエミュレーターが2xxで受け付けた件数")
    for service in services:
        if counts[service]:
            print(f"{service}: {counts[service]}件 例: {' / '.join(examples[service][:3])}")


if __name__ == "__main__":
    main()

サービス名だけを渡すと、そのサービスの一致率を出します。--opsに「サービス名:操作名」をカンマ区切りで渡すと、その操作を含むテストだけを数えます。

python3 emu_fidelity.py scores.json.gz manifest.jsonl s3 dynamodb sqs lambda \
  --ops s3:PutObject,s3:GetObject,dynamodb:PutItem,dynamodb:GetItem,dynamodb:Query,sqs:SendMessage,sqs:ReceiveMessage,lambda:Invoke

オブジェクトの保存・取得、項目の読み書き、メッセージの送受信、関数の呼出しという、よくある組み合わせで実行した結果は次のとおりです。

  • SQS:成功する操作97%、拒否される操作92%
  • DynamoDB:成功する操作89%、拒否される操作68%
  • S3:成功する操作87%、拒否される操作75%
  • Lambda(Invokeを含むテスト):成功する操作31%、拒否される操作29%

この組み合わせなら、キューとテーブルの処理はエミュレーターで繰り返し試し、Lambdaの関数本体は関数単体のテストか実際のAWSで確かめる、という分け方になります。サービス名は一覧のservice欄の表記(stepfunctions、events、bedrock-runtimeなど)、操作名はoperations欄の表記に合わせてください。

エージェントにテストを任せる前に決めておくこと

数字を踏まえると、エミュレーターに任せる範囲を次のように分けるのが現実的です。

  • エミュレーターで繰り返してよい:一致率が9割前後のサービス(SQS、KMS、S3・DynamoDBの正常系)。マージ前に一度、実際のAWSで同じテストを流す
  • 実際のAWSで確かめる:ポリシーの書式と権限、エラー処理や入力チェックのテスト、Lambda関数の実行結果
  • エミュレーターに頼らない:一致率が2〜4割のサービス(Systems Manager、Bedrock Runtime)と、使う操作にテストがない部分

実際のAWSを誤って呼ばない準備も必要です。DevelopersIOの検証は、環境変数の資格情報が設定ファイルより優先されることと、AWS_ENDPOINT_URLを設定し忘れたシェルでは実際のAWSが呼ばれることを指摘しています。AWSの公式ブログも、手順の中でAWS_ACCESS_KEY_IDなどをunsetしています。エージェントに渡すテストの冒頭で、接続先がエミュレーターかどうかを確かめる処理を入れておくと安心です。

エミュレーターは匿名の利用データとエラーデータを既定でAWSへ送ります。社内の方針で止める必要があれば、起動時に設定してください(DevelopersIOの検証では--telemetry off)。どのツールにエージェントを任せるかは、AIコーディングツールの料金と選び方の比較にまとめています。

この集計で分からないこと

  • エミュレーターは起動していません:数字はAWSが公開した採点結果を数え直したもので、手元での実測ではありません。
  • 一覧と照合できなかった採点が3,147件あります:評価スイートの一覧は11月11日16時(UTC)の版で、採点に使ったテストの一部が見つかりませんでした。上の数字はその分を含みません。
  • 失敗の理由は分かりません:採点結果は通過か失敗かを記録するだけで、理由は記録されていません。Lambdaの実行系が低い原因も確かめていません。
  • 数字は版ごとに変わります:エミュレーターは1日に何度も更新されます。比べるときは同じテスト版の採点どうしにし、使う版のscores.json.gzで数え直してください。
  • 検証したリージョンは1つです:AWSが本物との比較に使ったのはオレゴン(us-west-2)で、東京リージョンでの違いは反映されていません。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

IT業界で10年、ソフトウェア開発をしてきたエンジニアです。今もChatGPT、Codex、Claude Codeを中心に、開発の効率化や生産性向上にAIをフル活用しています。応用情報技術者。登壇や執筆もしています。このサイトでは、仕事でAIを使う立場から、料金や利用条件、使い方を調べてまとめています。

目次