05 – Testování kódu generovaného umělou inteligencí: Strategie kvality a rámce
2025 stanovil nepohodlnou pravdu: kód generovaný umělou inteligencí je funkční, rychle se vyrábí, často elegantní ve struktuře. Ale není to bezpečné. Podle Zabezpečení kódu Veracode GenAI Zpráva 2025, která analyzovala přes 100 LLM na 80 úlohách kódování v Javě, Pythonu, C# a JavaScript, 45 % kódu generovaného umělou inteligencí neprošlo bezpečnostními testy a představuje OWASP Top 10 zranitelností v kódové základně. 62 % má konstrukční chyby, nikoli zjevné chyby, ale chyby hluboké architektonické prvky, které se objevují až ve výrobě. A kód vytvořený AI zavádí 2,74krát větší zranitelnost ve srovnání s kódem napsaným lidskými vývojáři.
Tato data nejsou odsouzením kódování vibrací. Jsou připomínkou toho AI mění kdo kód píše, není potřeba jej testovat. Ve skutečnosti to zesiluje. Když a Agent AI vygeneruje 500 řádků kódu za 30 sekund, překážka produktivity se posouvá od zápisu po ověření. Kdo nemá robustní testovací strategii pro kód AI skončí s kódovou základnou, jejíž kvalita je neprůhledná, jejíž chyby jsou nepředvídatelné a jejíž jistota a sázka.
Tento článek vytváří tuto strategii. Pochopením jedinečných vzorců chyb kódu AI, prostřednictvím testování jednotek, testování založeného na vlastnostech, statické analýzy, SAST a testování mutací až po provozní kontrolní seznam pro rozhodnutí, kdy přijmout nebo odmítnout kód vygenerovaný asistentem. Cílem jsou vývojáři, kteří již používají nástroje AI v jejich pracovní postup a chtějí si vybudovat sebevědomí, že to budou dělat profesionálně.
Co se naučíte
- protože kód generovaný umělou inteligencí selhává jiným způsobem než lidský kód
- Specifické typy chyb: halucinované API, logická chyba, bezpečnostní díra
- Testování jednotek a testování založené na vlastnostech s pytestem a hypotézou
- Statická analýza pomocí ESLint a SonarQube nakonfigurovaných pro kód AI
- Testování zabezpečení pomocí Semgrep: vlastní pravidla pro vzory AI
- TDD s asistentem AI: cyklus Red-Green-Refactor se změní na Red-AI-Green-Review
- Automatická kontrola kódu prostřednictvím kanálu pro více agentů
- Metriky kvality: pokrytí, skóre mutace, cyklomatická složitost
- Provozní kontrolní seznam pro přijetí nebo odmítnutí kódu AI
Proč kód AI potřebuje specifické testování
Kód napsaný lidským vývojářem s sebou nese mentální model systému. Vývojář ví, co je nad a pod modulem, který píše, viděl kód běží ve výrobě, ví, že okraje domů hoří. AI tento kontext nemá. Opera na statistických vzorech extrahovaných z miliard řádků kódu a vytváří řešení, která jsou věrohodný vzhledem k výzvě, ne nutně opravit vzhledem k systému.
Tento rozdíl vytváří zásadně odlišný chybový profil. Lidský kód má tendenci předvídatelně selhat: unavený vývojář zkopíruje špatný kód, zapomene null-check, používá včerejší obchodní logiku pro dnešní model. Přesné chyby, sledovatelné, často odhalené existujícími testy. Kód AI strukturálně selže: implementuje nesprávné API s absolutní konzistencí, zavádí vzorec zranitelnosti prostřednictvím desítek funkcí vytváří architekturu, která dokonale funguje 95 % případů a kolaps na zbývajících 5 % s chováním, které nelze předvídat bez testovací sady k tomu určené.
Problém syntetické důvěry
AI generuje kód se stejnou spolehlivostí bez ohledu na správnost. A model, který halucinuje neexistující funkci knihovny, tak činí se stejným spolehlivost, se kterou implementuje správný algoritmus. Není tam ani náznak nejistoty v vytvořeném textu. Díky tomu je vizuální kontrola nespolehlivá: kód zdá se správně, protože je to dobře napsané. Pravdu odhalí pouze provedení.
Tři specifické vlastnosti kódu AI vyžadují odlišný přístup k testování z tradičního testování:
- Kontextová odchylka: Stejná výzva na různých kódových základnách produkuje kód s velmi rozdílnými profily kvality. Testování musí být specifické pro kontext nasazení, nejen abstraktní funkce.
- Vnitřní konzistence chyb: Pokud AI zavede špatný vzorec (například nezabezpečená manipulace s uživatelským vstupem), replikuje jej napříč všemi funkcemi podobný. Chyby nejsou izolované: jsou systémové. To vyžaduje testování, které hledá vzory, nejen jednotlivé případy.
- Datované znalosti: Modely mají hranice znalostí. Používají zastaralá rozhraní API, Zastaralé vzorce zabezpečení, závislosti se známými zranitelnostmi. Testování musí obsahovat ověřovací vrstva aktuálních závislostí a bezpečnostních vzorů.
Typy chyb v kódu generovaném AI
Než vytvoříte efektivní testovací strategii, musíte vědět, co hledáte. Kód AI vytváří odlišné kategorie chyb, z nichž každá má své vlastní vlastnosti vyžadují různé přístupy k detekci.
1. Halucinované API
Nejzákeřnější chyba a také nejzdokumentovanější. AI vymýšlí funkce, metody, parametry
a moduly, které neexistují, ale mají tak věrohodné názvy, že projdou vizuální kontrolou bez nich
obtížnost. Klasický příklad: pandas.DataFrame.filter_by_threshold() ne
existuje, ale zní to přesně jako něco, co by mělo existovat. Kód se zkompiluje (pokud
neexistuje žádná přísná kontrola typu), projde linting a selže za běhu s a
AttributeError který se objeví pouze při volání funkce s reálnými daty.
Primární obranou proti halucinovaným API je provedení, nikoli kontrola. Jednotkové testy které skutečně volají kód, a to i s minimálním vstupem, okamžitě detekují tento typ chyby. Statická kontrola typu s přísným mypy (Python) nebo TypeScript režim přidává ochrannou vrstvu pro dobu kompilace.
2. Logic Error a Off-by-One
Kód AI je překvapivě dobrý v jednoduché logice a překvapivě křehký s logikou na hranici. Pole indexy, včetně vs exkluzivní rozsahy, podmínky ukončení smyčky: Toto jsou body, kde kód AI zavádí chyby jemné, které standardní testy neodhalí, pokud nejsou speciálně navrženy zkušební limitní hodnoty.
3. Bezpečnostní otvor ze známých vzorů
Zpráva Veracode 2025 dokumentuje, že LLM se nedaří bránit proti XSS o 86 % relevantních případů a proti vstřikování log v 88 % případů. Toto nejsou náhodné chyby: jsou důsledkem skutečnosti, že trénovací data obsahují obrovské množství kódu zranitelné, které se model naučil replikovat. SQL injection, SSRF, procházení cesty, nesprávná deseralizace: systematické vzory, které vyžadují vyhrazený SAST, nejen testování funkční.
4. Chyba architektonického návrhu
Nejobtížnější odhalit pomocí automatických testů. AI vytváří řešení, která fungují lokálně, ale neškálovat, kteří mají správná rozhraní, ale nesprávné odpovědnosti, které splňují požadovanou smlouvu, ale porušují základní architektonické principy (jediná odpovědnost, oddělení zájmů, uvolněná vazba). 62% designová chyba citovaný Veracode spadá do této kategorie. Detekce vyžaduje kombinaci metriky složitosti, analýza závislostí a strukturovaná kontrola kódu.
Testovací rámec pro kód AI
Strategie testování jednotek: Testování hranic
Strategii testování jednotek pro kód AI je třeba posunout směrem k okrajovým hodnotám a nejednoznačné případy. Kód AI dobře zvládá případ šťastné cesty (a byl vyškolen z milionů příkladů případu šťastné cesty). Selhává na hranicích, na anomálních vstupech, na zpracování chyb.
Efektivní sada testů jednotek pro kód AI zahrnuje:
- Test se vstupem na hraně platného rozsahu (nulové, záporné, max-int hodnoty)
- Test s prázdnými vstupy, null, Žádný, prázdný řetězec
- Testuje vstup, který obsahuje speciální znaky nebo neočekávané kódování
- Testy, které ověřují chování v případě chyby (zpracování výjimek)
- Testy, které kontrolují vedlejší účinky (změny sdíleného stavu, databáze, souborového systému)
# 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
Testování založené na vlastnostech s hypotézou
Testování založené na vlastnostech je nejvýkonnější dostupný nástroj pro testování kódu AI. Místo toho, aby definovali jednotlivé testovací případy, definují sami sebe invariantní vlastnosti který musí platit pro jakýkoli vstup v zadané doméně a generuje hypotézu automaticky stovky vstupů k nalezení protipříkladů. Tato technika je zvláště účinný proti kódu AI, protože systematicky zkoumá prostor vstupů, včetně okrajových případů, které by nás nenapadlo testovat ručně.
Dokument z roku 2025 publikovaný na arXiv (Agentní testování založené na vlastnostech: Hledání chyb V celém ekosystému Pythonu) prokázal, že agent Claude Code, který generuje Testy hypotéz naleznou různé a doplňující se chyby ve srovnání s ručně psanými testy, s několika falešně pozitivními výsledky. Tento přístup funguje, protože hypotéza je postavena na Claude Code, který zná oblast testování.
# 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
Statická analýza: ESLint, SonarQube a vlastní pravidla
Statická analýza je první vrstvou obrany proti nekvalitnímu kódu AI. Provádí se bez nutnosti spouštění kódu a z tohoto důvodu je ideální integrovat v revizní smyčce nebo kanálu CI/CD jako povinné hradlo před kterýmkoli nasadit.
U kódu AI standardní konfigurace nástrojů statické analýzy ne
dost. Kód AI má tendenci vytvářet specifické vzory, které vyžadují vyhrazená pravidla:
příliš dlouhé funkce generované v jednom bloku, kruhové závislosti, nadměrné používání
z any v TypeScript, importy neexistujících nebo zastaralých modulů.
{
"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}]
}
}
]
}
Pro SonarQube je kromě standardní konfigurace užitečné nakonfigurovat a Brána kvality specifická pro kód AI s přísnějšími prahy: žádné problémy s blokováním, minimální pokrytí 80 %, maximální duplicita 3 %, a explicitní omezení cyklomatické složitosti funkcí.
Testování zabezpečení: SAST se Semgrep pro kód AI
Specializované SAST (Static Application Security Testing) pro kód AI jde ještě dále styl linting: hledá aktivní vzorce zranitelnosti, často neviditelné pro a ruční kontrolu, protože jsou syntakticky správné. Referenčním nástrojem je Semgrep pro tuto kategorii v roce 2025 s více než 20 000 předdefinovanými pravidly a schopností napsat vlastní pravidla, která přesně odpovídají vzorům, které AI zavádí.
V listopadu 2025 společnost Semgrep oznámila soukromou beta verzi své detekce poháněné umělou inteligencí systém, který kombinuje tradiční statickou analýzu s kontextovým uvažováním falešně pozitivních o 91 % ve srovnání se samostatnými SAST. Pro kód AI tento přístup hybridní a zvláště efektivní, protože rozumí kontextu, ve kterém vzor přichází používá, nejen jeho přítomnost.
# 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"
Kritická data: Veracode 2025
Zpráva Veracode 2025 analyzovala více než 100 LLM v 80 úlohách kódování. Java je nejvíce ohrožený jazyk 72% míra selhání zabezpečení. Python, C# a JavaScript jsou mezi 38 % a 45 %. Nejvíce alarmující věc: novější, pokročilejší modely nejsou o nic bezpečnější než ty starší. Bezpečnost kódu AI se s velikostí modelu nezlepšuje.
TDD s AI Assistant: Red-AI-Green-Review
Tradiční vývoj řízený testem se řídí cyklem Red-Green-Refactor: píšete test (který selže), implementujete minimum nutné k tomu, aby prošel, refaktorujete. S asistentem AI se tento cyklus změní v Red-AI-Green-Review: nejprve napíšete testy (zachování lidské kontroly nad specifikacemi), delegujete implementace se provádí na AI, ověřuje se, zda prošly všechny testy, a provádějí se kontroly kódu zaměřené na bezpečnostní a architektonické vzory.
Tento přístup má klíčovou výhodu: vývojáři psané testy představují skutečné specifikace systému, nikoli pochopení výzvy AI. Pokud AI generuje kód, který neprojde testy, problém je explicitní a měřitelný. Pokud kód projde všemi testy, ale má bezpečnostní problémy, Semgrep je najde. Pokud má chyby v návrhu, kontrola kódu je identifikuje. Cyklus je dokončen.
# 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
Automatizovaná kontrola kódu: Multi-Agent Pipeline
Multiagentní kanál pro kontrolu kódu pro AI kód kombinuje několik specialistů které fungují paralelně na různých aspektech kvality: bezpečnostní agent recenze, jedna o stylu kódu, jedna o výkonu, jedna o architektuře. Výsledek a strukturovaný přehled, který vývojář používá jako výchozí bod lidská kontrola, nikoli jako náhrada.
Tento přístup není nový (existují nástroje jako CodeClimate, DeepSource, Codacy let), ale v roce 2025 se díky kvalitě modelů stal plynovod životaschopný zcela agentní. Například Claude Code může být organizován Paralelní subagenti, kteří analyzují kód z různých úhlů a agregují výsledky v jedné revizní zprávě.
# 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}")
Metriky kvality pro kód AI
Testování kódu AI vyžaduje širší sadu metrik než pouhé pokrytí kódem. Pokrytí měří, kolik řádků kódu testy provedou, ale neměří kvalitu těch testů. AI kód může mít 90% pokrytí testy, které nic nekontrolují relevantní. Další potřebné metriky jsou:
Skóre mutace
Testování mutací je nejspolehlivější způsob, jak měřit kvalitu testů, a nejen to
jejich množství. Tester mutací (např mutmut pro Python nebo
Stryker pro JavaScript/TypeScript) systematicky zavádí malé
změny v kódu (mutanti: změna > in >=, obrací a
boolean, odstraní návrat) a ověří, že existující testy tyto změny detekují.
Pokud mutant přežije testy, testy nejsou dostatečně granulované.
Pro kód AI je minimální skóre cílové mutace 70 % (Zlatá úroveň). Skóre pod 50 % bez ohledu na to znamená neadekvátní sadu testů procentuální pokrytí.
# 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%)"
Cyklomatická složitost
Cyklomatická složitost měří počet lineárně nezávislých drah prostřednictvím funkce. Funkce se složitostí 1 má pouze jednu možnou cestu (žádné pobočky). Funkce se složitostí 20 má 20 cest a vyžaduje alespoň 20 testovacích případů pro kompletní pokrytí pobočky. AI kód má tendenci generovat funkce s vysokou složitostí, protože se snaží zvládnout všechny případy v jediné funkci monolitické.
Doporučené cíle pro kód AI v roce 2025 jsou: střední hodnota < 10 podle funkce, automatický příznak > 15 k povinné kontrole, např automatické odmítnutí > 25 jako brána CI/CD. Tyto hodnoty jsou přísnější lidského kódu, protože kód AI je velmi složitý a méně předvídatelný zpracování chyb s ohledem na složitý kód napsaný vývojářem.
Osvědčené postupy: Kontrolní seznam pro přijetí kódu AI
Následující kontrolní seznam představuje bránu minimální kvality před integrací Kód generovaný AI v produkční kódové základně. Není to úplný seznam, ale pokrývá nejčastější kategorie chyb zdokumentované v roce 2025.
Kontrolní seznam: Brána kvality pro kód generovaný AI
Vrstva 1 – Automatizované (brána CI/CD, blokování)
- Všechny testy jednotky prošly (0 selhání)
- Bezpečnostní kontrola Semgrep: 0 problém závažnosti ERROR
- ESLint/TypeScript: 0 chyb (upozornění jsou přijatelná s odůvodněním)
- Pokrytí kódu >= 80 % na nových linkách
- Žádné závislosti se známým CVE (npm/pip audit)
- Žádné napevno zakódované přihlašovací údaje (git-secrets, detect-secrets)
Vrstva 2 – Metriky kvality (upozornění, pokud nejsou respektovány)
- Skóre mutace >= 70 % na kritických modulech
- Cyklomatická složitost < 15 pro každou funkci
- Kognitivní složitost < 20 pro každou funkci
- Žádné funkce s více než 50 řádky
- Duplikace kódu < 3 %
Vrstva 3 – Ruční kontrola (povinné pro významné změny)
- Obchodní logika ověřena podle původních specifikací
- Upravené zpracování chyb: žádné tiché zachycení, žádná generická výjimka
- Případy hran dokumentované v testech: prázdný vstup, null, extrémní hodnoty
- Žádná volání externích rozhraní API bez časového limitu a logiky opakování
- Mutace v izolovaném stavu: žádné nedokumentované vedlejší účinky
- Vlastnosti založené na testech funkcí s numerickým nebo textovým vstupem
Vrstva 4 – Architectural Review (pro nové moduly nebo významné refaktoringy)
- Respektována jediná odpovědnost (jeden důvod pro změnu na třídu)
- Závislosti na abstrakcích, nikoli na konkrétních implementacích
- Žádné kruhové závislosti mezi moduly
- Stabilní a dobře zdokumentovaná veřejná rozhraní
Anti-Pattern: Co NEDĚLAT s kódem AI
- Nikdy se neslučujte bez spuštění testů: i na „malé korektury“. Kód AI je deterministický, ale není transparentní: změna výzvy se mění celou implementaci nezřejmými způsoby.
- Nikdy nevěřte, že „na mém počítači to funguje“: kód AI a zvláště citlivé na rozdíly prostředí. Testování CI/CD na nádobě čisté jsou povinné.
- Nikdy nepřijímejte kód AI na ověřovacích formulářích bez SAST: autentizace a autorizace jsou oblasti, kde je kód AI statisticky nejnebezpečnější. Zpráva Veracode uvádí JWT bez ověření, nesprávné správy relace a oprávnění eskalace mezi 5 nejčastějšími zranitelnostmi AI.
- Nepoužívejte AI ke generování testů pro kód AI: testy musí být napsaný vývojářem. Pokud požádáte AI, aby napsala testy pro sebe kódu, získáte testy, které ověří implementaci AI, nikoli specifika systému.
Závěry: Testování jako aktivátor Vibe Coding
Cílem tohoto článku není vyděsit lidi, kteří používají AI k psaní kódu. A naopak: budování důvěry, abyste jej mohli dobře používat. 45% bezpečnost míra selhání kódu AI neznamená, že přichází 45 % kódu AI výrobní a nebezpečné. To znamená, že bez strukturovaného ověřovacího procesu to je pravděpodobnost. Při správném postupu toto procento klesá drasticky.
Kombinace TDD (testy napsané před, ne po), testování založené na vlastnostech s Hypotéza, SAST se Semgrep, statická analýza s ESLint nakonfigurovanou pro vzory AI, a testování mutací k ověření kvality testů vytváří ochranný systém díky čemuž je vibrační kódování profesionální. Nebrzdí vývoj: stabilizuje ho.
Další článek v sérii zkoumá Prompt Engineering pro IDE e Generování kódu: jak psát výzvy, které produkují lepší kód, bezpečnější, udržitelnější, omezující následné testování.
Zdroje a užitečné odkazy
Vibe Coding a Agentic Development Series
Celá série
- Vibe Coding: Paradigma, které se změnilo v roce 2025
- Claude Code: Terminal Agent Development
- Agentické pracovní postupy: Dekompoziční problémy pro AI
- Multi-Agent Coding: LangGraph, CrewAI a AutoGen
- Testování kódu generovaného umělou inteligencí (tento článek)
- Prompt Engineering pro IDE a generování kódu
- Bezpečnost v kódování Vibe: Rizika a zmírnění
- Budoucnost vývoje agentů v roce 2026
Související statistiky
- Cursor IDE: První AI-First IDE - Jak Cursor zpracovává testování kódu AI
- OWASP Top 10 2025: Aktuální zranitelnosti - Kontext zranitelností, které AI přináší
- Claude pro Code Review - Použijte Clauda jako recenzenta







