コンテンツにスキップ

AI Code Import — テストケース設計

本ドキュメントは、code-import ワーカーのテストに関する 唯一の真実の情報源(Single Source of Truth)。以下を定義する:

  1. テスト戦略 — カテゴリ、レイヤー、責任範囲。
  2. テスト環境 — ローカル、CI、dev(デプロイ済 Lambda)、staging。
  3. テストフィクスチャ — サンプルペイロード、サンプルコード、サンプル画像。
  4. テストカタログ — 全テストケース(ID、目的、入力、期待出力、優先度)。
  5. カバレッジ目標 — 「完了」の定義。
  6. CI 統合 — PR とデプロイ毎の実行方法。

テスト対象の公式 I/O 契約は I/O 定義;技術詳細および処理フローは 概要。

以下のパスはリポジトリ上の apps/code-import/(src/code_import/ の Python パッケージ code_import)を前提とする。__tests__/ は現在のスキャフォールドに対する 目標配置 である。


1. テスト戦略

ピラミッド

     ┌──────────────────────┐
     │  E2E / スモーク (dev)  │   約 3 ケース  (実 AWS、実 OpenAI、実 DocDB)
     ├──────────────────────┤
     │  統合 (CI)             │   約 10 ケース (process_record をモック付きで)
     ├──────────────────────┤
     │  コントラクト (CI)     │   約 6 ケース  (スキーマ検証、Webhook 形式)
     ├──────────────────────┤
     │  ユニット (CI)          │   約 25 ケース (純粋関数、エージェント出力)
     └──────────────────────┘

カテゴリ

カテゴリ 範囲 実行場所 モック
ユニット 単一関数またはクラス CI(push 毎) 全外部(S3、OpenAI、DocDB)
コントラクト スキーマ/ペイロード検証 CI(push 毎) なし — 純粋データ
統合 process_record(record) 全体 CI(push 毎) S3、OpenAI、DocDB、Webhook(HTTP)
スモーク (dev) 実 Lambda 呼び出し 手動 / 夜間 なし
負荷 スループット & レイテンシ 手動 / リリース前 OpenAI(録画再生)

責任範囲

レイヤー 責任者 ツール
ユニット + コントラクト + 統合 PLABS.ID(ワーカー開発) pytest、pytest-asyncio、moto、mongomock、respx
スモーク (dev) PLABS.ID 運用 aws lambda invoke + pytest アサーション
負荷 PLABS.ID + 4D インフラ dev Lambda に対して locust

2. テスト環境

環境 目的 OpenAI S3 DocDB Webhook
local(開発 PC) ユニット + 統合 モック moto mongomock respx
ci(GitHub Actions) local と同じ モック moto mongomock respx
dev(デプロイ済 AWS) スモーク 実(gpt-5.4-nano) 実バケット 実クラスタ 実バックエンド
staging リリース前スモーク + 負荷 実 実 実 実

コストガード: 実 OpenAI を呼び出すテストは @pytest.mark.real_openai マーカーを宣言し、デフォルトでスキップ。pytest -m real_openai で有効化。


3. テストフィクスチャ

全フィクスチャは apps/code-import/__tests__/fixtures/ に配置。

3.1 サンプル SQS レコード

フィクスチャファイル バリアント
record_minimal.json 必須フィールドのみ(css_code、img_url なし)
record_with_screenshot.json img_url を含む全フィールド
record_with_css.json img_url 以外の全フィールド
record_full.json css_code と img_url を含む全フィールド
record_page.json type=0(page)
record_based_on_design.json based_on=1
record_based_on_wireframe.json based_on=2
record_invalid_uuid.json 不正な code_id
record_missing_field.json name 欠落
record_invalid_type.json type=2
record_empty_source_code.json source_code=""
record_huge_source_code.json 250 KB の source_code
record_url_encoded_s3.json %E3%83%95 形式のエスケープを含む img_url
record_double_wrapped.json Records 配列をネストする body(バックエンドのレガシーバグ対策)

3.2 サンプルソースコード

フィクスチャファイル 説明
code_button.tsx シンプルな Tailwind ボタン
code_modal.tsx フォーム付きモーダルダイアログ
code_table.tsx ソート可能なデータテーブル
code_page_login.tsx フルページ(type=0)
code_with_inline_styles.tsx クラスなし、インラインスタイルのみ

3.3 サンプルスクリーンショット

フィクスチャファイル フォーマット 備考
button_primary.png PNG 320x80、通常ケース
button_primary.jpg JPEG 同シーン、圧縮アーティファクトあり
large_image.png PNG 4 MB、アップロード上限近く
corrupt.png — 切り詰められたバイト列

3.4 サンプル LLM レスポンス(録画)

pytest-recording(または手書き)で fixtures/llm_responses/ 以下に録画。CI でエージェント出力を決定論的に再現するために使用。

フィクスチャ 入力先 返却値
semantic_button.json Code Analysis LLM(arch ノード C) ["submit","action","primary","cta","button"]
visual_button.json Vision LLM(arch ノード F/G) "角丸の青いボタン、白いサンセリフラベル、半径 8px、パディング 12px"
embedding_512.json OpenAI embeddings 512 次元の float ベクトル 3 本

4. テストカタログ

4.1 ユニットテスト — apps/code-import/__tests__/unit/

スキーマ検証(test_schemas.py)

ID 目的 入力 期待
U-SCH-001 必須フィールドのみ受理 record_minimal.json body CodeImportMessage インスタンス、css_code=None、img_url=None
U-SCH-002 全フィールド正しく代入 record_full.json body 全フィールドが期待値と一致
U-SCH-003 不正な UUID を拒否 record_invalid_uuid.json body code_id で ValidationError
U-SCH-004 必須フィールド欠落を拒否 record_missing_field.json body name で ValidationError
U-SCH-005 type 範囲外を拒否 record_invalid_type.json body type で ValidationError
U-SCH-006 based_on 範囲外を拒否 based_on=3 ValidationError
U-SCH-007 空 source_code を拒否 record_empty_source_code.json ValidationError
U-SCH-008 オプション css_code が None 受理 minimal + css_code=None OK
U-SCH-009 オプション img_url が None 受理 minimal + img_url=None OK

S3 URL パース(test_s3_helpers.py)

ID 目的 入力 期待
U-S3-001 s3:// URL をパース s3://bucket/key/file.png ("bucket", "key/file.png")
U-S3-002 仮想ホスト形式 HTTPS をパース https://bucket.s3.us-east-1.amazonaws.com/file.png ("bucket", "file.png")
U-S3-003 パス形式 HTTPS をパース https://s3.us-east-1.amazonaws.com/bucket/file.png ("bucket", "file.png")
U-S3-004 キーを URL デコード s3://bucket/%E3%83%95%E3%82%9A%E3%83%AD.png キーに フ°ロ.png を含む(unquote 済)
U-S3-005 未対応スキームを拒否 ftp://bucket/key ValueError

画像フォーマット検出(test_image_helpers.py)

ID 目的 入力 期待
U-IMG-001 PNG マジック検出 PNG 先頭 8 バイト "png"
U-IMG-002 JPEG マジック検出 JPEG 先頭 3 バイト "jpeg"
U-IMG-003 GIF マジック検出 b"GIF89a..." "gif"
U-IMG-004 WebP マジック検出 b"RIFF....WEBP" "webp"
U-IMG-005 未知フォーマットは None ランダムバイト None

埋め込みヘルパー(test_embedding.py)

ID 目的 入力 期待
U-EMB-001 3 入力で 3 本の 512 次元ベクトル openai.embeddings.create をモック 長さ 512 の list[float] 3 本のタプル
U-EMB-002 単一バッチ呼び出し クライアント spy API 呼び出しは正確に 1 回
U-EMB-003 空 semantic_words を綺麗に join [] 入力が "" になる(クラッシュなし)

エージェント出力モデル(test_agent_outputs.py)

ID 目的 入力 期待
U-AGT-001 CodeSemantics が 5 単語を受理 5 文字列のリスト OK
U-AGT-002 CodeSemantics が 10 単語を受理 10 文字列のリスト OK
U-AGT-003 CodeSemantics が 4 単語を拒否 4 文字列のリスト ValidationError
U-AGT-004 CodeSemantics が 11 単語を拒否 11 文字列のリスト ValidationError
U-AGT-005 VisualFeatures が短すぎる文字列を拒否 10 文字 ValidationError

4.2 コントラクトテスト — __tests__/contract/

ID 目的 入力 期待
C-WH-001 成功 Webhook が openapi 風スキーマと一致 ワーカーからの成功ペイロード ai-status webhook スキーマに準拠
C-WH-002 失敗 Webhook がスキーマと一致 ワーカーからの失敗ペイロード 準拠
C-WH-003 Webhook の type は常に "code-import" 任意の Webhook type == "code-import"
C-WH-004 Webhook の recordId が code_id を正確にエコー 成功パス recordId == code_id
C-WH-005 Webhook scope が SQS の tenant/project をエコー 成功・失敗ペイロード organizationId == organization_id かつ projectId == project_id
C-DOC-001 DocDB ドキュメントに必須の visual と semantics ブロック サンプル実行 両キー存在、metadata と vector_embedding を含む
C-DOC-002 vector_embedding の長さは 512 サンプル実行 両配列の長さ == 512

4.3 統合テスト — __tests__/integration/test_process_record.py

全テストで mongomock、moto、respx、録画 LLM レスポンスを使用。各テストは process_record(record) をエンドツーエンドで呼び出す。

ハッピーパス

ID 目的 フィクスチャ 期待 DB 書き込み 期待 Webhook
I-HP-001 最小ペイロード、スクショなし、CSS なし record_minimal.json css_code=null、visual.metadata.image_url=null、両埋め込み 512 次元 success、keywords 長さ 5–10
I-HP-002 スクショ付き全フィールド record_full.json visual.metadata.image_url == img_url success
I-HP-003 CSS あり、スクショなし record_with_css.json css_code 保存、visual.metadata.image_url=null success
I-HP-004 Page(type=0) record_page.json type=0 保存 success
I-HP-005 based_on=1(design) record_based_on_design.json based_on=1 保存 success
I-HP-006 based_on=2(wireframe) record_based_on_wireframe.json based_on=2 保存 success
I-HP-007 URL エンコード S3 キー record_url_encoded_s3.json S3 キー正しくデコード、画像ダウンロード success
I-HP-008 冪等な再処理 record_full.json を 2 回実行 2 回目は upsert、ドキュメント形状不変 成功 Webhook 2 回
I-HP-009 二重ラップ body(レガシー) record_double_wrapped.json 内部 body 抽出、通常処理 success

失敗パス

Repository regression test では write conflict 時に新しい session で transaction 全体を再試行し、index 無効化の原子性を保ち、AI 呼び出しを繰り返さないことを確認する。永続エラーはローカルで再試行せず、再試行上限後は SQS record failure とする。

ID 目的 フィクスチャ / モック 期待動作
I-FP-001 空の body 文字列 {"body":""} ValueError、Webhook なし(code_id なし)、例外を再 raise
I-FP-002 不正な JSON body {"body":"{not json"} JSONDecodeError、Webhook なし、再 raise
I-FP-003 Pydantic 検証失敗 record_invalid_uuid.json ValidationError、部分 Webhook 試行(ベストエフォート)、再 raise
I-FP-004 S3 NoSuchKey ダウンロード時 ClientError(NoSuchKey) をモック Webhook failed、再 raise
I-FP-005 S3 AccessDenied ClientError(AccessDenied) をモック Webhook failed、再 raise
I-FP-006 LLM semantic タイムアウト semantic agent が AgentRunError を raise Webhook failed、再 raise
I-FP-007 LLM visual タイムアウト visual agent が AgentRunError を raise Webhook failed、再 raise
I-FP-008 埋め込み API レート制限 openai.RateLimitError Webhook failed、再 raise
I-FP-009 DocumentDB 書き込み失敗 mongomock が PyMongoError を raise Webhook failed、再 raise
I-FP-010 Webhook 自体の失敗(5xx) respx が 500 を返却 log して SQS retry のため再 raise
I-FP-011 Webhook 4xx(認証不正) respx が 401 を返却 log して SQS retry のため再 raise

エッジケース

ID 目的 フィクスチャ 期待
I-EC-001 ソースコードが 200 KB 上限 record_huge_source_code.json 受理、処理
I-EC-002 非 ASCII(日本語、ベトナム語)を含むソースコード unicode source_code DocDB へ正しくラウンドトリップ
I-EC-003 画像が JPEG(PNG ではない) button_primary.jpg 警告ログ、vision agent はバイト受信
I-EC-004 画像 4 MB 超 large_image.png 受理(実 Lambda は 6 MB ペイロード上限、5 MB で警告)
I-EC-005 破損画像バイト corrupt.png vision agent エラー → Webhook failed
I-EC-006 1 SQSEvent に 2 レコード 2 レコードのバッチ 両方処理、最初が失敗しても 2 番目を試行

4.4 スモークテスト — __tests__/smoke/

デプロイ済 dev Lambda に対してのみ実行。@pytest.mark.smoke 付き。

ID 目的 手順 期待
S-001 実エンドツーエンド(スクショあり) 1. SQS 送信 record_full.json。2. ≤ 3 分待機。3. DocDB を code_id で照会。4. PG code 行を照会。 DocDB ドキュメントに両埋め込み存在、PG 行 status=1(completed)
S-002 実エンドツーエンド(スクショなし) record_with_css.json 送信。同じ待機 + 確認。 上記と同じ、visual.metadata.image_url=null
S-003 実エンドツーエンド(失敗パス) 不正な S3 URL 付きペイロード送信。 3 分以内に PG 行 status=2(failed)、Webhook ログに failed

4.5 負荷テスト — __tests__/load/

各リリース前に実行。@pytest.mark.load 付き。

ID 目的 プロファイル 合格基準
L-001 定常スループット 10 msg/min を 30 分 p50 < 90 秒、p95 < 180 秒、エラー率 < 1 %
L-002 バースト処理 1 分で 100 msg、その後アイドル 10 分以内に全処理完了、DLQ メッセージなし
L-003 持続バースト 30 msg/min を 1 時間 DLQ なし、OpenAI レート制限超過なし

5. カバレッジ目標

レイヤー 行カバレッジ 分岐カバレッジ 備考
apps/code-import/src/code_import/handler.py ≥ 90 % ≥ 85 % Lambda エントリ(lambda_handler) — 全 try/except 分岐をカバーすること
apps/code-import/src/code_import/schemas.py 100 % 100 % Pydantic モデル — 網羅的にテスト
packages/agentic/agents/code_semantics.py ≥ 80 % — ビルダーのみ、LLM ランタイムはモック
packages/agentic/agents/code_visual.py ≥ 80 % — 同上
packages/helpers/embedding.py 100 % — 単一関数
packages/helpers/webhook.py ≥ 90 % ≥ 85 % リトライパスを含む
packages/models/documentdb/code.py ≥ 85 % — インデックス作成の冪等性

実行:

pytest --cov=code_import --cov=packages \
       --cov-report=term-missing --cov-fail-under=85

6. CI 統合

PR 毎のパイプライン(.github/workflows/test-code-import.yml)

on:
  pull_request:
    paths:
      - 'apps/code-import/**'
      - 'packages/agentic/agents/code_*.py'
      - 'packages/helpers/**'
      - 'packages/models/documentdb/code.py'

jobs:
  test:
    steps:
      - uv sync
      - pytest -m "not real_openai and not smoke and not load" \
               --cov --cov-fail-under=85
      - ruff check apps/code-import packages
      - mypy apps/code-import packages

夜間(dev に対して)

on:
  schedule:
    - cron: '0 18 * * *'   # 03:00 JST

jobs:
  smoke:
    steps:
      - uv sync
      - pytest -m smoke

リリース前(staging に対して)

-m "smoke or load" を実行する手動トリガーワークフロー。

失敗時のポリシー

CI ステージ 失敗時
Lint / 型 / ユニット / コントラクト / 統合 マージ阻止
カバレッジ閾値未満 マージ阻止
夜間スモーク #guinness-ai Slack に通知、3 連続失敗で issue 作成
負荷テスト リリース阻止、マージは阻止しない

7. クイックテストマトリクス(チートシート)

シナリオ テスト ID
必須フィールドのみ U-SCH-001、I-HP-001
全オプションフィールドあり U-SCH-002、I-HP-002
Page vs code I-HP-004
全 based_on バリアント I-HP-005、I-HP-006
スクショ欠落 I-HP-001、I-HP-003
不正入力(検証) U-SCH-003 .. U-SCH-007、I-FP-003
外部サービス障害 I-FP-004 .. I-FP-009
Webhook 障害の握りつぶし I-FP-010、I-FP-011
冪等性 I-HP-008
実世界エンドツーエンド S-001、S-002
スループット L-001、L-002

8. Definition of Done(PR 毎)

code-import の PR は以下を満たした時点でマージ可能:

  • [ ] 該当する全テストケースが実装され、合格している。
  • [ ] カバレッジ閾値達成(§5 参照)。
  • [ ] 動作変更があれば最低 1 つの新規統合テスト追加。
  • [ ] SQS スキーマ、Webhook スキーマ、または DocDB ドキュメント形状が変わった場合、S-001–S-003 のスモークを dev で手動検証。
  • [ ] PR 説明に該当テスト ID をリンク。

リポジトリ全体のリリース準備状況については docs/overview/non-functional.ja.md(SLA / SLO 目標)参照。