05 - AI 生成コードのテスト: 品質戦略とフレームワーク
2025 年は不都合な真実を確立しました。AI によって生成されたコードは機能し、迅速に生成され、 多くの場合、構造がエレガントです。しかし、安全ではありません。によると、 Veracode GenAI コードのセキュリティ レポート2025、Java、Python、C# の 80 のコーディング タスクで 100 以上の LLM を分析しました。 そしてJavaScript、 AI が生成したコードの 45% がセキュリティ テストに不合格 そして紹介します OWASP コードベースのトップ 10 の脆弱性。 62% には設計上の欠陥があり、明らかなバグではなくエラーがあります。 本番環境でのみ現れる奥深い建築上の特徴。そしてAIによって生成されたコードは、 2.74倍の脆弱性 人間の開発者が書いたコードと比較してください。
このデータはバイブコーディングを非難するものではありません。それらは、次のことを思い出させてくれます。 AIが変える コードをテストする必要はなく、誰がコードを書くか。実際、それはそれを増幅させます。とき AI エージェントが 30 秒で 500 行のコードを生成し、生産性のボトルネックが変化 執筆から検証まで。 AI コードに対する堅牢なテスト戦略を持たない人はいない 最終的には、品質が不透明で、バグが予測不可能で、 安全と賭け。
この記事ではその戦略を構築します。固有のエラーパターンを理解することで 単体テスト、プロパティベースのテスト、静的分析、SAST および 突然変異テスト、いつ受け入れるか拒否するかを決定するための運用チェックリストまで アシスタントによって生成されたコード。ターゲットは、既に AI ツールを使用している開発者です。 自分のワークフローを知り、それを専門的に行う自信を築きたいと考えています。
何を学ぶか
- AI が生成したコードは人間のコードとは異なる方法で失敗するため
- 特定のバグの種類: 幻覚 API、ロジック エラー、セキュリティ ホール
- pytest と Hypothesis を使用した単体テストとプロパティベースのテスト
- AI コード用に構成された ESLint と SonarQube を使用した静的分析
- Semgrep を使用したセキュリティ テスト: AI パターンのカスタム ルール
- AI アシスタントを使用した TDD: Red-Green-Refactor サイクルは Red-AI-Green-Review になります
- マルチエージェントパイプラインによる自動コードレビュー
- 品質指標: カバレッジ、突然変異スコア、循環的複雑さ
- AI コードを受け入れるか拒否するための運用チェックリスト
AI コードに特別なテストが必要な理由
人間の開発者によって書かれたコードには、システムのメンタル モデルが組み込まれています。 開発者は、自分が書いているモジュールの上下にあるものを知っており、コードを見たことがあります。 本番稼働中、エッジハウスが燃えていることを知っています。 AI にはこのコンテキストがありません。オペラ 数十億行のコードから抽出された統計パターンに基づいて、次のようなソリューションを生成します。 もっともらしい プロンプトが表示されると、必ずしもそうではありません 正しい システムを考えると。
この違いにより、根本的に異なるエラー プロファイルが生成されます。人間のコード 予想どおりに失敗する傾向があります。疲れた開発者は間違ったコードをコピーし、忘れてしまいます。 null チェックでは、昨日のビジネス ロジックを今日のモデルに使用します。時間厳守、 追跡可能であり、多くの場合、既存のテストによって明らかになります。 AI コードは構造的に失敗しています。 絶対的な一貫性を備えた間違った API を実装し、脆弱性パターンを導入します 数十の機能を通じて、完璧に機能するアーキテクチャを構築します。 95% のケースが発生し、残りの 5% は予測不可能な行動で崩壊します。 このために設計されたテストスイートがなければ。
総合的な信頼の問題
AI は、正確さに関係なく、同じ信頼度でコードを生成します。あ ライブラリの存在しない機能を幻覚させるモデルは、同じ機能を使って幻覚を起こします。 正しいアルゴリズムを実装しているという自信。不確実性の兆候はない 生成されたテキストで。これにより、視覚的なレビューの信頼性が低くなります: コード そうみたいです それはよく書かれているからです。処刑のみが真実を明らかにする。
AI コードの 3 つの特有の特性には、異なるテスト アプローチが必要です 従来のテストから:
- 状況による差異: 異なるコードベースで同じプロンプトが生成される 非常に異なる品質プロファイルを持つコード。テストは特定のものでなければなりません 単なる抽象的な機能ではなく、展開コンテキスト。
- エラーの内部一貫性: AIが間違ったパターンを導入した場合 (たとえば、ユーザー入力の安全でない処理)、それをすべての関数にわたって複製します。 似たような。バグは孤立したものではなく、全身に存在します。これには、次のことを求めるテストが必要です 単一のケースだけでなく、パターンも考慮します。
- 古い知識: モデルには知識の限界があります。非推奨の API を使用しているため、 時代遅れのセキュリティ パターン、既知の脆弱性を伴う依存関係。テストには以下を含める必要があります 現在の依存関係とセキュリティ パターンの検証層。
AI が生成したコードのバグの種類
効果的なテスト戦略を構築する前に、何を探しているのかを知る必要があります。 AI コードは異なるカテゴリのバグを生成し、それぞれに独自の特性があります。 異なる検出アプローチが必要です。
1.幻覚API
最も潜伏性の高いバグであり、最も文書化されているバグでもあります。 AI が関数、メソッド、パラメーターを発明
存在しないが、非常にもっともらしい名前が付いているため、モジュールなしでもビジュアル レビューに合格するモジュール
難しさ。典型的な例: pandas.DataFrame.filter_by_threshold() そうではない
それは存在しますが、それはまさに存在すべきもののように聞こえます。コードはコンパイルされます (
厳密な型チェックはありません)、lint には合格しますが、実行時にエラーが発生して失敗します。
AttributeError これは、関数が実際のデータで呼び出された場合にのみ表示されます。
幻覚 API に対する主な防御策は、レビューではなく実行です。単体テスト 実際にコードを呼び出すものは、最小限の入力でもすぐに検出されます このタイプのエラー。 mypy (Python) または TypeScript strict を使用した静的型チェック モードはコンパイル時に保護層を追加します。
2. ロジックエラーとオフバイワン
AI コードは単純なロジックは驚くほど得意ですが、驚くほど脆弱です ロジックを境界に持つ。配列インデックス、包含範囲と排他範囲、条件 ループ終了: AI コードがエラーを引き起こすポイントです。 特別に設計されていない場合、標準テストでは検出できない微妙なもの テスト限界値。
3. 既知のパターンによるセキュリティ ホール
2025 Veracode Report には、LLM が XSS に対する防御に 86% 失敗していることが文書化されています。 関連するケースの 88% でログ インジェクションに反対しています。これらはランダムなバグではありません。 これは、トレーニング データに膨大な量のコードが含まれているという事実の結果です。 モデルが複製することを学習しているため、脆弱です。 SQL インジェクション、SSRF、パス トラバーサル、 不適切な逆シリアル化: テストだけでなく専用の SAST を必要とする体系的なパターン 機能的。
4. 建築設計上の欠陥
自動テストで検出するのが最も困難です。 AI は機能するソリューションを構築します ローカルだがスケールしない、適切なインターフェースを持っているが間違った責任を負っている、 必要な契約には準拠しているが、基本的なアーキテクチャ原則に違反しているもの (単一責任、関心事の分離、疎結合)。 62% 設計上の欠陥 Veracode によって引用されているものは、このカテゴリーに分類されます。検出には以下の組み合わせが必要です 複雑さのメトリクス、依存関係分析、構造化コードレビュー。
AI コードのテスト フレームワーク
単体テスト戦略: 境界テスト
AI コードの単体テスト戦略はエッジ値に向けて移行する必要がある そして明らかではないケース。 AI コードはハッピー パスのケースを適切に処理します (訓練されています) ハッピーパスケースの何百万もの例の中から)。境界や異常な入力では失敗します。 エラー処理について。
AI コードの効果的な単体テスト スイートには次のものが含まれます。
- 有効範囲の端にある入力を使用してテストします (ゼロ、負、max-int 値)。
- 空の入力、null、None、空の文字列を使用したテスト
- 特殊文字または予期しないエンコーディングを含む入力を使用したテスト
- エラー発生時の動作を検証するテスト(例外処理)
- 副作用 (共有状態、データベース、ファイルシステムへの変更) をチェックするテスト
# test_ai_generated.py
# Strategia di testing per codice generato da AI
import pytest
from hypothesis import given, strategies as st, settings
from hypothesis import HealthCheck
# Funzione generata da AI (esempio)
# def process_user_input(data: dict) -> dict:
# return {
# "name": data["name"].strip().title(),
# "age": int(data["age"]),
# "email": data["email"].lower()
# }
from my_module import process_user_input
class TestAIGeneratedBoundaries:
"""Test dei valori limite per codice AI-generated."""
def test_nominal_case(self):
"""Happy path - il caso che l'AI ha quasi certamente gestito bene."""
result = process_user_input({
"name": "mario rossi",
"age": "30",
"email": "MARIO@EXAMPLE.COM"
})
assert result["name"] == "Mario Rossi"
assert result["age"] == 30
assert result["email"] == "mario@example.com"
def test_empty_name(self):
"""Edge case: nome vuoto - spesso non gestito dall'AI."""
with pytest.raises((ValueError, KeyError)):
process_user_input({"name": "", "age": "25", "email": "a@b.com"})
def test_negative_age(self):
"""Edge case: eta negativa - l'AI spesso non valida il range."""
with pytest.raises(ValueError, match="age must be positive"):
process_user_input({"name": "Test", "age": "-5", "email": "a@b.com"})
def test_missing_required_field(self):
"""Edge case: campo mancante - gestione KeyError o default?"""
with pytest.raises(KeyError):
process_user_input({"name": "Test", "age": "25"})
def test_sql_injection_in_name(self):
"""Security: input injection non deve passare invariato."""
malicious = "'; DROP TABLE users; --"
result = process_user_input({
"name": malicious,
"age": "25",
"email": "a@b.com"
})
# Il nome deve essere sanitizzato o rifiutato
assert "DROP TABLE" not in result.get("name", "")
def test_extremely_long_input(self):
"""Edge case: input molto lungo - buffer overflow / ReDoS."""
long_name = "a" * 10000
# Non deve appendere indefinitamente o crashare
with pytest.raises((ValueError, OverflowError)):
process_user_input({"name": long_name, "age": "25", "email": "a@b.com"})
def test_unicode_name(self):
"""Compatibilità Unicode: nomi non-ASCII."""
result = process_user_input({
"name": "giuseppe gallo",
"age": "28",
"email": "giuseppe@example.it"
})
assert result["name"] == "Giuseppe Gallo"
class TestAIGeneratedWithMocks:
"""Test con mock per isolare le dipendenze."""
def test_database_not_called_on_validation_error(self, mocker):
"""Verifica che il DB non venga chiamato se la validazione fallisce."""
mock_db = mocker.patch("my_module.database.save")
with pytest.raises(ValueError):
process_user_input({"name": "", "age": "25", "email": "a@b.com"})
mock_db.assert_not_called() # L'AI spesso chiama il DB prima di validare
仮説を使用したプロパティベースのテスト
プロパティベースのテストは、AI コードのテストに使用できる最も強力なツールです。 個々のテスト ケースを定義するのではなく、テスト ケース自体を定義します。 不変のプロパティ これは、指定されたドメイン内のすべての入力に対して成立する必要があり、仮説が生成されます。 自動的に何百もの入力を入力して反例を見つけます。このテクニックは 体系的に空間を探索するため、AI コードに対して特に効果的です 手動でテストすることは考えられなかったエッジケースを含む、入力の数。
arXiv で公開された 2025 年の論文 (エージェントのプロパティベースのテスト: バグの発見 Python エコシステム全体で) エージェント クロード コードが生成することを実証しました。 仮説テストでは、手動で作成したテストと比較して、異なる補完的なバグが見つかります。 誤検知はほとんどありません。このアプローチが機能するのは、仮説が基づいて構築されているためです。 クロード・コード、テストの領域を知っています。
# test_properties.py
# Property-based testing con Hypothesis per codice AI
import pytest
from hypothesis import given, strategies as st, settings, assume
from hypothesis import HealthCheck
from decimal import Decimal
# Funzione AI-generated: calcolo sconto
# def calculate_discount(price: float, discount_percent: float) -> float:
# if discount_percent < 0 or discount_percent > 100:
# raise ValueError("Discount must be between 0 and 100")
# return price * (1 - discount_percent / 100)
from pricing import calculate_discount
# Proprietà 1: idempotenza - sconto 0% non cambia il prezzo
@given(price=st.floats(min_value=0.01, max_value=1_000_000.0, allow_nan=False))
def test_zero_discount_preserves_price(price):
"""Sconto zero deve restituire il prezzo originale."""
result = calculate_discount(price, 0.0)
assert abs(result - price) < 0.01 # tolleranza floating point
# Proprietà 2: monotonia - sconto maggiore = prezzo minore
@given(
price=st.floats(min_value=1.0, max_value=10000.0, allow_nan=False),
discount1=st.floats(min_value=0.0, max_value=50.0, allow_nan=False),
discount2=st.floats(min_value=50.0, max_value=100.0, allow_nan=False),
)
def test_higher_discount_lower_price(price, discount1, discount2):
"""Sconto maggiore deve produrre prezzo inferiore o uguale."""
result1 = calculate_discount(price, discount1)
result2 = calculate_discount(price, discount2)
assert result1 >= result2
# Proprietà 3: range output - prezzo finale sempre tra 0 e prezzo originale
@given(
price=st.floats(min_value=0.0, max_value=1_000_000.0, allow_nan=False),
discount=st.floats(min_value=0.0, max_value=100.0, allow_nan=False),
)
def test_output_range(price, discount):
"""Il prezzo scontato deve essere nel range [0, price]."""
result = calculate_discount(price, discount)
assert 0.0 <= result <= price + 0.01 # piccola tolleranza FP
# Proprietà 4: boundary - sconto 100% deve azzerare il prezzo
@given(price=st.floats(min_value=0.01, max_value=1_000_000.0, allow_nan=False))
def test_full_discount_zero_price(price):
"""Sconto del 100% deve risultare in prezzo zero."""
result = calculate_discount(price, 100.0)
assert abs(result) < 0.01
# Proprietà 5: validazione input - discount fuori range solleva eccezione
@given(discount=st.one_of(
st.floats(max_value=-0.001, allow_nan=False),
st.floats(min_value=100.001, allow_nan=False, allow_infinity=False)
))
def test_invalid_discount_raises(discount):
"""Discount fuori range [0, 100] deve sollevare ValueError."""
assume(not (discount != discount)) # esclude NaN
with pytest.raises(ValueError):
calculate_discount(100.0, discount)
# Test con st.composite per dati correlati
@st.composite
def valid_order(draw):
"""Strategia composita per ordini validi."""
price = draw(st.floats(min_value=1.0, max_value=10000.0, allow_nan=False))
discount = draw(st.floats(min_value=0.0, max_value=99.9, allow_nan=False))
return {"price": price, "discount": discount}
@given(order=valid_order())
@settings(max_examples=500, suppress_health_check=[HealthCheck.too_slow])
def test_order_processing_invariants(order):
"""Invarianti su ordini casuali generati da Hypothesis."""
result = calculate_discount(order["price"], order["discount"])
# Il risultato deve essere un numero finito
assert result == result # esclude NaN
assert result < float('inf')
# Il risultato deve essere non negativo
assert result >= 0.0
静的分析: ESLint、SonarQube、カスタム ルール
静的分析は、低品質の AI コードに対する防御の最初の層です。 コードを実行することなく実行されるため、統合するのが理想的です。 コミット ループまたは CI/CD パイプライン内で、任意の前の必須ゲートとして 展開します。
AI コードの場合、静的解析ツールの標準構成では、
十分です。 AI コードは、専用のルールを必要とする特定のパターンを生成する傾向があります。
単一ブロック内で生成された長すぎる関数、循環依存関係、過度の使用
の any TypeScript では、存在しないモジュールまたは非推奨のモジュールのインポート。
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"ecmaVersion": 2024
},
"plugins": ["@typescript-eslint", "security", "sonarjs"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/strict",
"plugin:security/recommended",
"plugin:sonarjs/recommended"
],
"rules": {
// Regole critiche per codice AI-generated
// Proibisce 'any' esplicito - l'AI lo usa spesso per "sicurezza"
"@typescript-eslint/no-explicit-any": "error",
// Richiede return type esplicito - l'AI spesso lo omette
"@typescript-eslint/explicit-function-return-type": ["error", {
"allowExpressions": false,
"allowTypedFunctionExpressions": true
}],
// Cognitive complexity: l'AI genera funzioni lunghe e complesse
"sonarjs/cognitive-complexity": ["error", 15],
// Duplicazione: l'AI replica pattern identici in funzioni diverse
"sonarjs/no-duplicate-string": ["warn", 3],
"sonarjs/no-identical-functions": "error",
// Security: pattern comuni nel codice AI vulnerabile
"security/detect-object-injection": "error",
"security/detect-non-literal-regexp": "error",
"security/detect-possible-timing-attacks": "error",
"security/detect-unsafe-regex": "error",
// Complessità funzione - l'AI genera monoliti
"max-lines-per-function": ["warn", {
"max": 50,
"skipBlankLines": true,
"skipComments": true
}],
// Nesting profondo - pattern frequente nel codice AI
"max-depth": ["warn", 4],
// Promise handling - l'AI dimentica spesso await
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/await-thenable": "error",
// Null safety
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/strict-null-checks": "error",
// Import di moduli non utilizzati (hallucinated imports)
"@typescript-eslint/no-unused-vars": "error",
"no-unused-vars": "off" // sostituito da typescript version
},
"overrides": [
{
// Configurazione più stringente per codice generato da AI
"files": ["**/generated/**/*.ts", "**/ai-output/**/*.ts"],
"rules": {
"sonarjs/cognitive-complexity": ["error", 10],
"max-lines-per-function": ["error", {"max": 30}]
}
}
]
}
SonarQube の場合、標準構成に加えて、 AI コードに特化したクオリティ ゲート より厳しいしきい値を使用すると、次のようになります。 ブロッキングの問題なし、最小カバレッジ 80%、最大重複 3%、 そして関数の循環的複雑さに対する明示的な制限。
セキュリティ テスト: AI コード用の Semgrep を使用した SAST
AI コードに特化した SAST (静的アプリケーション セキュリティ テスト) がさらに進化 style lint: 多くの場合、ユーザーには見えない、アクティブな脆弱性パターンを探します。 構文的に正しいため、手動で確認してください。 Semgrep は参照ツールです 2025 年にはこのカテゴリに向けて、20,000 以上の事前定義ルールと、 AI が導入するパターンと正確に一致するカスタム ルールを作成します。
2025 年 11 月、Semgrep は AI を活用した検出のプライベート ベータ版を発表しました。 従来の静的分析と状況に応じた推論を組み合わせて、 スタンドアロン SAST と比較して、誤検知が 91% 減少します。 AI コードの場合、このアプローチは ハイブリッドであり、パターンが発生するコンテキストを理解するため、特に効果的です その存在だけでなく、使用されます。
# semgrep-ai-patterns.yaml
# Regole custom Semgrep per pattern di vulnerabilità tipici del codice AI-generated
rules:
# Regola 1: SQL injection via f-string o concatenazione
- id: ai-sql-injection-fstring
patterns:
- pattern: |
cursor.execute(f"...{$VAR}...")
- pattern: |
cursor.execute("..." + $VAR + "...")
message: |
Potenziale SQL injection via interpolazione diretta.
L'AI genera spesso query con f-string invece di parametri.
Usa cursor.execute("SELECT ... WHERE id = %s", (var,))
languages: [python]
severity: ERROR
metadata:
category: security
owasp: "A03:2021 - Injection"
ai-pattern: true
# Regola 2: Path traversal in file operations
- id: ai-path-traversal
patterns:
- pattern: |
open($BASE + $USER_INPUT, ...)
- pattern: |
open(os.path.join($BASE, $USER_INPUT), ...)
message: |
Potenziale path traversal. L'AI spesso concatena path utente
senza sanitizzazione. Usa pathlib.Path().resolve() e verifica
che il path risultante sia dentro la directory consentita.
languages: [python]
severity: ERROR
# Regola 3: Hardcoded credentials (pattern AI molto comune)
- id: ai-hardcoded-credentials
patterns:
- pattern: |
password = "$VALUE"
- pattern: |
api_key = "$VALUE"
- pattern: |
secret = "$VALUE"
- pattern: |
token = "$VALUE"
message: |
Credenziali hardcoded rilevate. L'AI inserisce spesso
placeholder realistici che sembrano credenziali reali.
Usa variabili d'ambiente o un secret manager.
languages: [python, javascript, typescript]
severity: ERROR
# Regola 4: Eval su input esterno
- id: ai-eval-injection
patterns:
- pattern: eval($USER_INPUT)
- pattern: exec($USER_INPUT)
message: |
eval/exec su input controllabile dall'utente. Pattern pericoloso
che l'AI introduce quando genera interpreti o sistemi dinamici.
languages: [python, javascript]
severity: ERROR
# Regola 5: Deserializzazione non sicura (Python pickle)
- id: ai-unsafe-deserialization
patterns:
- pattern: pickle.loads($DATA)
- pattern: pickle.load($FILE)
message: |
pickle.loads/load su dati non fidati. L'AI usa pickle
per semplicità senza considerare le implicazioni di sicurezza.
Usa json.loads() o librerie sicure come marshmallow.
languages: [python]
severity: ERROR
# Regola 6: JWT senza verifica firma
- id: ai-jwt-no-verification
patterns:
- pattern: |
jwt.decode($TOKEN, options={"verify_signature": False})
message: |
JWT decodificato senza verifica firma. Pattern frequente
nel codice AI generato per ambienti di sviluppo che poi
finisce in produzione.
languages: [python]
severity: ERROR
# Regola 7: Regex potenzialmente catastrofica (ReDoS)
- id: ai-catastrophic-regex
pattern-regex: '\(\.\*\)+|\(\.\+\)+'
message: |
Pattern regex potenzialmente vulnerabile a ReDoS.
L'AI genera spesso regex con backtracking esponenziale.
languages: [python, javascript, typescript]
severity: WARNING
#!/bin/bash
# scan-ai-code.sh
# Script per security scan di codice generato da AI
set -euo pipefail
AI_CODE_DIR="{{$1:-src}}"
RULES_FILE="semgrep-ai-patterns.yaml"
REPORT_FILE="security-report.json"
echo "Avvio security scan su: $AI_CODE_DIR"
# Scan con regole custom AI + ruleset OWASP
semgrep scan \
--config "$RULES_FILE" \
--config "p/owasp-top-ten" \
--config "p/python" \
--json \
--output "$REPORT_FILE" \
"$AI_CODE_DIR"
# Analizza risultati
ERRORS=$(jq '.results | map(select(.extra.severity == "ERROR")) | length' "$REPORT_FILE")
WARNINGS=$(jq '.results | map(select(.extra.severity == "WARNING")) | length' "$REPORT_FILE")
echo "Risultati: $ERRORS error(s), $WARNINGS warning(s)"
# Fail se ci sono error (non warning)
if [ "$ERRORS" -gt 0 ]; then
echo "FAIL: $ERRORS vulnerabilità critiche rilevate nel codice AI"
jq '.results[] | select(.extra.severity == "ERROR") | {file: .path, line: .start.line, message: .extra.message}' "$REPORT_FILE"
exit 1
fi
echo "Security scan completato con successo"
重要なデータ: Veracode 2025
Veracode 2025 レポートでは、80 のコーディング タスクにわたって 100 以上の LLM を分析しました。 Java は最も危険にさらされている言語です。 セキュリティ失敗率 72%。 Python、C#、JavaScript は 38% ~ 45% です。最も憂慮すべきこと: 新しい、より高度なモデルが古いモデルよりも安全であるということはありません。セキュリティ AI コードのパフォーマンスはモデル サイズによって改善されません。
AI アシスタントを使用した TDD: Red-AI-Green-Review
従来のテスト駆動開発は、Red-Green-Refactor サイクルに従います。 テストが失敗した場合は、テストを成功させるために必要な最小限の機能を実装し、リファクタリングします。 AI アシスタントを使用すると、このサイクルは次のようになります。 赤-AI-緑-レビュー: あなたが最初にテストを書き(仕様に対する人間の制御を維持し)、あなたは委任します AI への実装が行われ、すべてのテストが合格することが検証され、コード レビューが実行されます。 セキュリティとアーキテクチャ パターンに重点を置いています。
このアプローチには、開発者が作成したテストという重要な利点があります。 これらはシステムの実際の仕様を表すものであり、AI がプロンプトを理解するものではありません。 AI がテストに失敗するコードを生成した場合、その問題は明白であり、測定可能です。 コードがすべてのテストに合格してもセキュリティ上の問題がある場合、Semgrep はそれらを検出します。 設計上の欠陥がある場合は、コードレビューによってそれらが特定されます。サイクルが完了しました。
# Passo 1: Lo sviluppatore scrive i test PRIMA
# test_payment_processor.py
import pytest
from decimal import Decimal
class TestPaymentProcessor:
"""
Specifiche per PaymentProcessor scritte PRIMA
di chiedere all'AI di implementarlo.
"""
def test_process_valid_payment(self, processor):
"""Pagamento valido deve restituire transaction_id."""
result = processor.process(
amount=Decimal("99.99"),
currency="EUR",
card_token="tok_valid_test"
)
assert result.success is True
assert result.transaction_id is not None
assert len(result.transaction_id) == 36 # UUID format
def test_negative_amount_rejected(self, processor):
"""Importo negativo deve sollevare ValueError."""
with pytest.raises(ValueError, match="Amount must be positive"):
processor.process(
amount=Decimal("-10.00"),
currency="EUR",
card_token="tok_valid_test"
)
def test_unsupported_currency_rejected(self, processor):
"""Valuta non supportata deve sollevare ValueError."""
with pytest.raises(ValueError, match="Currency not supported"):
processor.process(
amount=Decimal("50.00"),
currency="XYZ",
card_token="tok_valid_test"
)
def test_invalid_token_raises_payment_error(self, processor):
"""Token non valido deve sollevare PaymentError, non crashare."""
with pytest.raises(PaymentError, match="Invalid card token"):
processor.process(
amount=Decimal("50.00"),
currency="EUR",
card_token=""
)
def test_duplicate_idempotency(self, processor):
"""Stesso idempotency_key non deve creare duplicati."""
result1 = processor.process(
amount=Decimal("25.00"),
currency="EUR",
card_token="tok_valid_test",
idempotency_key="key-123"
)
result2 = processor.process(
amount=Decimal("25.00"),
currency="EUR",
card_token="tok_valid_test",
idempotency_key="key-123"
)
assert result1.transaction_id == result2.transaction_id
def test_amount_precision_preserved(self, processor):
"""Precisione decimale deve essere mantenuta."""
result = processor.process(
amount=Decimal("0.01"),
currency="EUR",
card_token="tok_valid_test"
)
assert result.charged_amount == Decimal("0.01")
# Passo 2: Si chiede ad AI di implementare PaymentProcessor
# Prompt: "Implementa PaymentProcessor che passa tutti questi test.
# Usa Decimal per gli importi, valida input,
# gestisci l'idempotency, non usare float."
# Passo 3: Si verifica che l'implementazione AI passi tutti i test
# pytest test_payment_processor.py -v
# Passo 4: Si esegue Semgrep sull'implementazione
# semgrep --config semgrep-ai-patterns.yaml payment_processor.py
# Passo 5: Code review focalizzata su sicurezza e architettura
自動コードレビュー: マルチエージェントパイプライン
AI コードのマルチエージェント コード レビュー パイプラインは、複数の専門家を組み合わせます 品質のさまざまな側面を並行して実行する: セキュリティ エージェント 1 つはコード スタイルに関するレビュー、1 つはパフォーマンスに関するレビュー、1 つはアーキテクチャに関するレビューです。結果 開発者が開始点として使用する構造化レポート 代替品としてではなく、人間によるレビューです。
このアプローチは新しいものではありません (CodeClimate、DeepSource、Codacy などのツールがあります) しかし、2025 年には、モデルの品質によりパイプラインが実行可能になりました。 完全にエージェント的。たとえば、Claude Code は次のように調整できます。 コードをさまざまな角度から分析して集約する並列サブエージェント 結果を 1 つのレビュー レポートにまとめます。
# ai_code_review_pipeline.py
# Pipeline multi-agent per review di codice AI-generated
import asyncio
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import List
@dataclass
class ReviewResult:
agent: str
severity: str # "critical", "high", "medium", "low", "info"
category: str
message: str
file: str
line: int | None = None
async def run_security_agent(file_path: str) -> List[ReviewResult]:
"""Agente security: esegue Semgrep + bandit."""
results = []
# Semgrep scan
proc = await asyncio.create_subprocess_exec(
"semgrep", "--config", "p/owasp-top-ten",
"--json", file_path,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, _ = await proc.communicate()
import json
data = json.loads(stdout)
for finding in data.get("results", []):
results.append(ReviewResult(
agent="security",
severity="critical" if finding["extra"]["severity"] == "ERROR" else "high",
category="security",
message=finding["extra"]["message"],
file=finding["path"],
line=finding["start"]["line"]
))
return results
async def run_complexity_agent(file_path: str) -> List[ReviewResult]:
"""Agente complessità: analizza cyclomatic complexity."""
results = []
proc = await asyncio.create_subprocess_exec(
"radon", "cc", "-s", "-n", "B", file_path,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, _ = await proc.communicate()
for line in stdout.decode().splitlines():
if line.strip():
# radon output: " M 15:4 method_name - B (7)"
parts = line.strip().split()
if len(parts) >= 5:
results.append(ReviewResult(
agent="complexity",
severity="medium" if "B" in parts else "high",
category="maintainability",
message=f"Funzione con alta complessità: {' '.join(parts)}",
file=file_path
))
return results
async def run_coverage_agent(test_file: str, source_file: str) -> List[ReviewResult]:
"""Agente coverage: verifica che i test coprano il codice AI."""
results = []
proc = await asyncio.create_subprocess_exec(
"pytest", test_file,
f"--cov={source_file}",
"--cov-report=json:coverage.json",
"--quiet",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
await proc.communicate()
import json
try:
with open("coverage.json") as f:
cov_data = json.load(f)
total = cov_data["totals"]["percent_covered"]
if total < 80.0:
results.append(ReviewResult(
agent="coverage",
severity="high",
category="testing",
message=f"Coverage insufficiente: {total:.1f}% (minimo 80%)",
file=source_file
))
except FileNotFoundError:
results.append(ReviewResult(
agent="coverage",
severity="critical",
category="testing",
message="Impossibile calcolare la coverage. Test mancanti?",
file=source_file
))
return results
async def review_ai_code(
source_file: str,
test_file: str | None = None
) -> dict:
"""Pipeline completa di review per codice AI-generated."""
tasks = [
run_security_agent(source_file),
run_complexity_agent(source_file),
]
if test_file:
tasks.append(run_coverage_agent(test_file, source_file))
# Esecuzione parallela di tutti gli agenti
all_results = await asyncio.gather(*tasks, return_exceptions=True)
findings: List[ReviewResult] = []
for result in all_results:
if isinstance(result, Exception):
print(f"Agente fallito: {result}")
else:
findings.extend(result)
# Aggregazione risultati
critical = [f for f in findings if f.severity == "critical"]
high = [f for f in findings if f.severity == "high"]
return {
"total_findings": len(findings),
"critical": len(critical),
"high": len(high),
"approve": len(critical) == 0 and len(high) == 0,
"findings": findings
}
# Uso
if __name__ == "__main__":
result = asyncio.run(review_ai_code(
source_file="payment_processor.py",
test_file="test_payment_processor.py"
))
print(f"Review completata: {result['total_findings']} findings")
print(f"Approvato: {result['approve']}")
if not result["approve"]:
print("\nISSUE CRITICHE:")
for f in result["findings"]:
if f.severity in ["critical", "high"]:
print(f" [{f.severity.upper()}] {f.file}:{f.line} - {f.message}")
AI コードの品質指標
AI コードのテストには、単純なコード カバレッジよりも広範なメトリクスのセットが必要です。 カバレッジはテストが実行するコードの行数を測定しますが、品質を測定するものではありません それらのテストの。 AI コードは何もチェックしないテストで 90% をカバーできる 関連性のある。必要な追加の指標は次のとおりです。
突然変異スコア
突然変異テストは、テストの品質を測定する最も信頼できる方法ですが、それだけではありません。
それらの量。突然変異テスター ( ムムムト Pythonの場合または
ストライカー for JavaScript/TypeScript) は、体系的に小さなものを導入します。
コードの変更 (ミュータント: 変更) > in >=、を反転します
ブール値、リターンを削除) し、既存のテストがこれらの変更を検出することを検証します。
変異体がテストに生き残った場合、テストの粒度は十分ではありません。
AI コードの場合、ターゲット突然変異スコアの最小値は次のとおりです。 70% (ゴールド層)。 スコアが 50% 未満の場合は、テスト スイートが不適切であることを示します。 カバレッジの割合。
# setup.cfg - Configurazione mutmut
[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m pytest tests/ -x -q
tests_dir=tests/
dict_synonyms=
# Escludi file di configurazione e init
exclude=setup.py,conftest.py
# Esegui mutation testing
# mutmut run
# Visualizza risultati
# mutmut results
# Esempio di output:
# Mutant #1: SURVIVED
# src/payment_processor.py:45
# - if amount <= 0:
# + if amount < 0:
#
# Indica che il test per amount=0 non esiste!
# Aggiungere: assert raises ValueError for amount=0
# Script per CI/CD con soglia minima
#!/bin/bash
mutmut run --no-progress
KILLED=$(mutmut results | grep "Killed" | grep -o '[0-9]*' | head -1)
TOTAL=$(mutmut results | grep "Total" | grep -o '[0-9]*' | head -1)
if [ -z "$TOTAL" ] || [ "$TOTAL" -eq 0 ]; then
echo "Nessun mutante generato"
exit 0
fi
SCORE=$(echo "scale=2; $KILLED * 100 / $TOTAL" | bc)
echo "Mutation score: $SCORE%"
# Soglia minima 70% per codice AI
if (( $(echo "$SCORE < 70" | bc -l) )); then
echo "FAIL: Mutation score insufficiente per codice AI ($SCORE% < 70%)"
exit 1
fi
echo "OK: Mutation score accettabile ($SCORE%)"
循環的複雑性
循環的複雑度は、線形に独立したパスの数を測定します。 関数を通じて。複雑度 1 の関数には可能なパスが 1 つだけあります (支店はありません)。複雑度 20 の関数には 20 のパスがあり、少なくとも 完全なブランチ カバレッジのための 20 のテスト ケース。 AI コードが生成される傾向がある すべてのケースを 1 つの関数で処理しようとするため、関数は非常に複雑になります モノリシック。
2025 年の AI コードの推奨ターゲットは次のとおりです。 中央値 < 10 機能別に、 自動フラグ > 15 必須のレビューについては、e 自動拒否 > 25 CI/CDゲートとして。これらの値はより厳格です AI コードは非常に複雑で、予測可能性が低いため、人間のコードに比べて 開発者が作成した複雑なコードに関するエラー処理。
ベスト プラクティス: AI コードを受け入れるためのチェックリスト
次のチェックリストは、統合前の最低限の品質ゲートを示しています。 実稼働コードベース内の AI 生成コード。完全なリストではありませんが、 2025 年に文書化された最も頻繁なエラー カテゴリをカバーしています。
チェックリスト: AI 生成コードの品質ゲート
レイヤ 1 - 自動化 (CI/CD ゲート、ブロッカー)
- すべての単体テストに合格 (失敗は 0)
- セキュリティ スキャン Semgrep: 重大度 0 の問題エラー
- ESLint/TypeScript: エラー 0 (正当な理由があれば警告は許容されます)
- 新しい行のコード カバレッジ >= 80%
- 既知の CVE (npm/pip Audit) との依存関係なし
- ハードコードされた認証情報 (git-secrets、detect-secrets) はありません
レイヤ 2 - 品質メトリクス (遵守されていない場合は警告)
- 重要なモジュールの変異スコア >= 70%
- 各関数の循環的複雑度 < 15
- 各機能の認知複雑度 < 20
- 50行を超える関数はありません
- コードの重複 < 3%
レイヤ 3 - 手動レビュー (重大な変更の場合は必須)
- ビジネスロジックが元の仕様に照らして検証される
- 改訂されたエラー処理: サイレント キャッチなし、一般的な例外なし
- テストで文書化されたエッジケース: 空の入力、NULL、極値
- タイムアウトと再試行ロジックなしで外部 API を呼び出すことはできません
- 孤立状態の突然変異: 文書化されていない副作用なし
- 数値またはテキスト入力による関数のプロパティベースのテスト
レイヤー 4 - アーキテクチャのレビュー (新しいモジュールまたは重要なリファクタリング用)
- 単一責任の尊重 (クラスごとに変更理由は 1 つ)
- 具体的な実装ではなく抽象化への依存関係
- モジュール間に循環依存関係がない
- 安定した、十分に文書化されたパブリック インターフェイス
アンチパターン: AI コードでやってはいけないこと
- テストを実行せずにマージしないでください。 「ちょっとした修正」でも。 AI コードは決定的ですが透過的ではありません。プロンプトを変更すると変更されます。 実装全体を非自明な方法で実行します。
- 「私のコンピュータでは動作する」ということを決して信じないでください。 AIコード そして特に環境の違いに敏感です。コンテナ上での CI/CD テスト 清潔であることは必須です。
- SAST を使用しない場合は、認証フォームで AI コードを決して受け入れないでください。 認証と承認は、AI コードが統計的に最も危険な領域です。 Veracode レポートでは、検証なしの JWT、不適切なセッション管理、権限が引用されています。 escalation among the top-5 AI vulnerabilities.
- AI コードのテストを生成するために AI を使用しないでください。 テストは必ず行う必要があります 開発者によって書かれました。 AI に独自のテストを作成するよう依頼すると、 コードを作成すると、システムの詳細ではなく、AI の実装を検証するテストが得られます。
結論: Vibe コーディングのイネーブラーとしてのテスト
この記事の目的は、AI を使用してコードを作成する人々を怖がらせることではありません。 そしてその逆は、それをうまく使いこなすための自信を築くことです。 45% のセキュリティ AI コードの失敗率は、入ってくる AI コードの 45% を意味するわけではありません。 生産性が高く安全ではありません。つまり、構造化された検証プロセスがなければ、 それが確率です。適切なプロセスを行えば、その割合は下がります 劇的に。
TDD (テストは後ではなく前に書かれる)、プロパティベースのテストと 仮説、Semgrep を使用した SAST、AI パターン用に構成された ESLint を使用した静的分析、 テストの品質を検証するための突然変異テストにより、安全装置システムが作成されます。 これにより、バイブコーディングがプロフェッショナルになります。開発を遅らせるのではなく、安定させます。
シリーズの次の記事では、 IDE eの迅速なエンジニアリング コード生成: より良いコードを生成するプロンプトの書き方、 安全性と保守性が向上し、下流のテスト作業が軽減されます。
リソースと役立つリンク
Vibe コーディングとエージェント開発シリーズ
シリーズ全体
関連する洞察
- カーソル IDE: 初の AI ファースト IDE - Cursor が AI コード テストを処理する方法
- OWASP トップ 10 2025: 現在の脆弱性 - AI によってもたらされる脆弱性の背景
- コードレビューのクロード - クロードをレビューエージェントとして使用する







