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は400MalformedPolicy、エミュレーターは204(成功) - IAM:3件。誤ったポリシー文書での
CreatePolicy・CreateRole・PutUserPolicyを、AWSは400MalformedPolicyDocumentで拒否、エミュレーターは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)で、東京リージョンでの違いは反映されていません。
