05 - Testowanie kodu generowanego przez sztuczną inteligencję: strategie i ramy jakości
Rok 2025 ustalił niewygodną prawdę: kod wygenerowany przez sztuczną inteligencję jest funkcjonalny, szybki w wykonaniu, często o eleganckiej strukturze. Ale to nie jest bezpieczne. Według Bezpieczeństwo kodu Veracode GenAI Raport 2025, w którym przeanalizowano ponad 100 LLM na 80 zadań związanych z kodowaniem w językach Java, Python, C# i JavaScript, 45% kodu generowanego przez sztuczną inteligencję nie przechodzi testów bezpieczeństwa i przedstawia OWASP Top 10 luk w kodzie. 62% ma wady projektowe, nie oczywiste błędy, ale błędy głębokie cechy architektoniczne, które pojawiają się dopiero w produkcji. A kod wyprodukowany przez sztuczną inteligencję wprowadza 2,74 razy większa podatność na zagrożenia w porównaniu z kodem napisanym przez programistów.
Dane te nie stanowią potępienia kodowania wibracyjnego. Są o tym przypomnieniem AI zmienia kto pisze kod, a nie konieczność jego testowania. Rzeczywiście, to je wzmacnia. Kiedy A Agent AI generuje 500 linii kodu w 30 sekund, przesuwa się wąskie gardło w produktywności od napisania do weryfikacji. Kto nie ma solidnej strategii testowania kodu AI kończy się bazą kodu, której jakość jest nieprzejrzysta, której błędy są nieprzewidywalne i której bezpieczeństwo i zakład.
Ten artykuł buduje tę strategię. Poprzez zrozumienie unikalnych wzorców błędów kodu AI, poprzez testy jednostkowe, testy oparte na właściwościach, analizę statyczną, SAST i badania mutacji, aż do operacyjnej listy kontrolnej umożliwiającej podjęcie decyzji o przyjęciu lub odrzuceniu kod wygenerowany przez asystenta. Celem są programiści, którzy już korzystają z narzędzi AI w środowisku swój przepływ pracy i chcą zbudować pewność siebie, aby móc robić to profesjonalnie.
Czego się nauczysz
- ponieważ kod wygenerowany przez sztuczną inteligencję zawodzi w inny sposób niż kod ludzki
- Określone typy błędów: halucynacje API, błąd logiczny, luka w zabezpieczeniach
- Testy jednostkowe i testy oparte na właściwościach za pomocą pytest i hipotezy
- Analiza statyczna za pomocą ESLint i SonarQube skonfigurowanych pod kątem kodu AI
- Testowanie bezpieczeństwa za pomocą Semgrep: niestandardowe reguły dla wzorców AI
- TDD z asystentem AI: cykl Red-Green-Refactor staje się Red-AI-Green-Review
- Zautomatyzowany przegląd kodu za pośrednictwem potoku wieloagentowego
- Metryki jakości: zasięg, wynik mutacji, złożoność cykliczna
- Operacyjna lista kontrolna umożliwiająca zaakceptowanie lub odrzucenie kodu AI
Dlaczego kod AI wymaga specjalnych testów
Kod napisany przez programistę-człowieka niesie ze sobą mentalny model systemu. Programista wie, co znajduje się nad i pod modułem, który pisze, widział kod uruchomiony w produkcji, zna granice płonących domów. AI nie ma takiego kontekstu. Opery na wzorcach statystycznych wyodrębnionych z miliardów linii kodu i generuje rozwiązania, które są wiarygodny biorąc pod uwagę monit, niekoniecznie prawidłowy biorąc pod uwagę system.
Ta różnica powoduje zasadniczo inny profil błędów. Kod ludzki ma tendencję do przewidywalnych niepowodzeń: zmęczony programista kopiuje zły kod, zapomina kontrola zerowa, wykorzystuje wczorajszą logikę biznesową dla dzisiejszego modelu. Błędy punktualne, identyfikowalne, często ujawniane na podstawie istniejących testów. Kod AI zawodzi strukturalnie: implementuje niewłaściwe API z absolutną spójnością, wprowadza wzorzec podatności poprzez dziesiątki funkcji tworzy architekturę, która doskonale się sprawdza 95% przypadków, a w pozostałych 5% załamuje się z zachowaniami, których nie można przewidzieć bez zestawu testów przeznaczonego do tego celu.
Problem zaufania syntetycznego
AI generuje kod z taką samą pewnością, niezależnie od poprawności. A model, który halucynuje nieistniejącą funkcję biblioteki, robi to samo pewności, z jaką implementuje poprawny algorytm. Nie ma oznak niepewności w powstałym tekście. To sprawia, że ocena wizualna jest niewiarygodna: kod wydaje się racja, bo jest dobrze napisana. Dopiero egzekucja odkrywa prawdę.
Trzy specyficzne cechy kodu AI wymagają innego podejścia do testowania z tradycyjnych testów:
- Odchylenie kontekstowe: Tworzy ten sam monit w różnych bazach kodu kod o bardzo różnych profilach jakości. Testowanie musi być specyficzne dla kontekst wdrożenia, a nie tylko abstrakcyjna funkcjonalność.
- Wewnętrzna spójność błędów: Jeśli sztuczna inteligencja wprowadzi zły wzór (na przykład niebezpieczna obsługa danych wejściowych użytkownika), replikuje to we wszystkich funkcjach podobne. Błędy nie są izolowane: mają charakter systemowy. Wymaga to testów, które poszukują wzorce, a nie pojedyncze przypadki.
- Przestarzała wiedza: Modele mają granice wiedzy. Używają przestarzałych interfejsów API, Przestarzałe wzorce bezpieczeństwa, zależności ze znanymi lukami. Testowanie musi obejmować warstwa weryfikacji bieżących zależności i wzorców bezpieczeństwa.
Rodzaje błędów w kodzie generowanym przez sztuczną inteligencję
Zanim zbudujesz skuteczną strategię testowania, musisz wiedzieć, czego szukasz. Kod AI generuje odrębne kategorie błędów, z których każda ma swoją własną charakterystykę wymagają różnych metod wykrywania.
1. Halucynacyjne API
Najbardziej podstępny błąd i najlepiej udokumentowany. AI wymyśla funkcje, metody i parametry
oraz moduły, które nie istnieją, ale mają nazwy na tyle prawdopodobne, że bez nich przechodzą ocenę wizualną
trudność. Klasyczny przykład: pandas.DataFrame.filter_by_threshold() nie
istnieje, ale brzmi dokładnie tak, jak coś, co powinno istnieć. Kod się kompiluje (jeśli
nie ma ścisłego sprawdzania typu), przechodzi linting i kończy się niepowodzeniem w czasie wykonywania z a
AttributeError co pojawia się tylko wtedy, gdy funkcja jest wywoływana z rzeczywistymi danymi.
Podstawową obroną przed halucynacyjnymi interfejsami API jest wykonanie, a nie przeglądanie. Testy jednostkowe które faktycznie wywołują kod, nawet przy minimalnym wejściu, wykrywają natychmiast tego typu błąd. Statyczne sprawdzanie typu za pomocą mypy (Python) lub ścisłego TypeScript mode dodaje warstwę ochronną do czasu kompilacji.
2. Błąd logiczny i wyłączenie po jednym
Kod AI jest zaskakująco dobry w prostej logice i zaskakująco delikatny z logiką na granicy. Indeksy tablicowe, zakresy włączające i wyłączne, warunki zakończenie pętli: Są to punkty, w których kod AI wprowadza błędy subtelne, których standardowe testy nie wykrywają, jeśli nie są do tego specjalnie zaprojektowane wartości graniczne testu.
3. Luka w zabezpieczeniach ze znanych wzorców
Raport Veracode z 2025 r. dokumentuje, że LLM nie chronią przed XSS o 86% odpowiednich przypadków i przed wstrzyknięciem logu w 88% przypadków. Nie są to przypadkowe błędy: są konsekwencją faktu, że dane szkoleniowe zawierają ogromne ilości kodu podatne na zagrożenia, które model nauczył się replikować. Wstrzyknięcie SQL, SSRF, przejście ścieżki, niewłaściwa deserializacja: systematyczne wzorce wymagające dedykowanego SAST, a nie tylko testowania funkcjonalny.
4. Wada projektu architektonicznego
Najtrudniejszy do wykrycia za pomocą testów automatycznych. AI tworzy rozwiązania, które działają lokalnie, ale bez skalowania, którzy mają odpowiednie interfejsy, ale złe obowiązki, które są zgodne z wymaganą umową, ale naruszają podstawowe zasady architektoniczne (pojedyncza odpowiedzialność, rozdzielenie interesów, luźne powiązanie). 62% błędów projektowych cytowane przez Veracode należy do tej kategorii. Wykrywanie wymaga połączenia metryki złożoności, analiza zależności i przegląd kodu strukturalnego.
Ramy testowania kodu AI
Strategia testów jednostkowych: testowanie graniczne
Strategię testów jednostkowych dla kodu AI należy przesunąć w stronę wartości brzegowych i nieoczywistych przypadkach. Kod AI dobrze radzi sobie ze sprawą szczęśliwej ścieżki (i został przeszkolony spośród milionów przykładów przypadku szczęśliwej ścieżki). Zawodzi na granicach, na nietypowych wejściach, na temat obsługi błędów.
Efektywny zestaw testów jednostkowych dla kodu AI obejmuje:
- Test z danymi wejściowymi na granicy prawidłowego zakresu (wartości zero, ujemne, max-int)
- Testuj z pustymi wejściami, null, None, pustym ciągiem
- Testuje dane wejściowe zawierające znaki specjalne lub nieoczekiwane kodowanie
- Testy weryfikujące zachowanie w przypadku błędu (obsługa wyjątków)
- Testy sprawdzające skutki uboczne (zmiany w stanie udostępnionym, bazie danych, systemie plików)
# 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
Testowanie oparte na właściwościach z hipotezą
Testowanie oparte na właściwościach to najpotężniejsze narzędzie dostępne do testowania kodu AI. Zamiast definiować indywidualne przypadki testowe, definiują siebie niezmienne właściwości które muszą obowiązywać dla wszelkich danych wejściowych w określonej domenie, a Hipoteza generuje automatycznie setki danych wejściowych w celu znalezienia kontrprzykładów. Ta technika jest szczególnie skuteczny przeciwko kodowi sztucznej inteligencji, ponieważ systematycznie bada przestrzeń wejść, w tym przypadki brzegowe, których nie pomyślelibyśmy o testowaniu ręcznie.
Artykuł z 2025 r. opublikowany na arXiv (Testowanie oparte na właściwościach agenta: znajdowanie błędów W całym ekosystemie Pythona) wykazało, że agent Claude Code generujący Testy hipotez znajdują inne i uzupełniające się błędy w porównaniu z testami pisanymi ręcznie, z kilkoma fałszywymi alarmami. Podejście to działa, ponieważ opiera się na hipotezie Claude Code, znający dziedzinę testowania.
# 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
Analiza statyczna: ESLint, SonarQube i reguły niestandardowe
Analiza statyczna to pierwsza warstwa obrony przed kodem AI o niskiej jakości. Odbywa się to bez konieczności uruchamiania kodu, dlatego idealnie nadaje się do integracji w pętli zatwierdzeń lub potoku CI/CD jako obowiązkowa bramka przed jakąkolwiek wdrożyć.
W przypadku kodu AI standardowa konfiguracja narzędzi do analizy statycznej tego nie robi
wystarczy. Kod AI ma tendencję do tworzenia określonych wzorców, które wymagają dedykowanych reguł:
zbyt długie funkcje generowane w jednym bloku, zależności cykliczne, nadmierne użycie
z any w TypeScript, import nieistniejących lub przestarzałych modułów.
{
"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}]
}
}
]
}
W przypadku SonarQube, oprócz standardowej konfiguracji, przydatne jest skonfigurowanie a Brama jakości specyficzna dla kodu AI z bardziej rygorystycznymi progami: brak problemów z blokowaniem, minimalne pokrycie 80%, maksymalne powielanie 3%, oraz wyraźne ograniczenie cyklomatycznej złożoności funkcji.
Testowanie bezpieczeństwa: SAST z Semgrep dla kodu AI
Specjalistyczny SAST (statyczne testowanie bezpieczeństwa aplikacji) dla kodu AI idzie dalej styl linting: szuka aktywnych wzorców podatności, często niewidocznych dla a ręcznego przeglądu, ponieważ są poprawne pod względem składniowym. Semgrep jest narzędziem referencyjnym dla tej kategorii w 2025 r., z ponad 20 000 predefiniowanych reguł i możliwością napisz niestandardowe reguły, które dokładnie pasują do wzorców wprowadzanych przez sztuczną inteligencję.
W listopadzie 2025 r. firma Semgrep ogłosiła prywatną wersję beta swojego systemu wykrywania opartego na sztucznej inteligencji system, który łączy w sobie tradycyjną analizę statyczną z rozumowaniem kontekstowym w celu ograniczenia wyniki fałszywie dodatnie o 91% w porównaniu z samodzielnymi testami SAST. W przypadku kodu AI takie podejście hybrydowy i szczególnie skuteczny, ponieważ rozumie kontekst, w którym pojawia się dany wzór używany, a nie tylko jego obecność.
# 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"
Dane krytyczne: Veracode 2025
W raporcie Veracode 2025 przeanalizowano ponad 100 LLM w ramach 80 zadań związanych z kodowaniem. Java jest językiem najbardziej zagrożonym Wskaźnik awaryjności zabezpieczeń wynoszący 72%.. Python, C# i JavaScript stanowią od 38% do 45%. Najbardziej niepokojąca rzecz: nowsze, bardziej zaawansowane modele nie są bezpieczniejsze od starszych. Bezpieczeństwo kodu AI nie poprawia się wraz z rozmiarem modelu.
TDD z asystentem AI: recenzja Red-AI-Green
Tradycyjny rozwój oparty na testach opiera się na cyklu Red-Green-Refactor: piszesz test (który się nie powiedzie), wdrażasz minimum niezbędne do jego zaliczenia, przeprowadzasz refaktoryzację. Dzięki asystentowi AI cykl ten zamienia się w Recenzja Red-AI-Green: najpierw piszesz testy (zachowując ludzką kontrolę nad specyfikacjami), delegujesz wdrażanie odbywa się w sztucznej inteligencji, wszystkie testy są weryfikowane i przeprowadzane są przeglądy kodu koncentruje się na wzorcach bezpieczeństwa i architekturze.
Takie podejście ma kluczową zaletę: testy napisane przez programistę reprezentują rzeczywiste specyfikacje systemu, a nie zrozumienie podpowiedzi przez sztuczną inteligencję. Jeśli sztuczna inteligencja generuje kod, który nie przechodzi testów, problem jest wyraźny i mierzalny. Jeśli kod przejdzie wszystkie testy, ale ma problemy z bezpieczeństwem, Semgrep je znajdzie. Jeśli ma wady projektowe, przegląd kodu je identyfikuje. Cykl jest zakończony.
# 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
Zautomatyzowany przegląd kodu: potok wieloagentowy
Wieloagentowy proces przeglądu kodu AI łączy kilku specjalistów które działają równolegle w różnych aspektach jakości: agent bezpieczeństwa recenzja, jedna o stylu kodu, druga o wydajności, druga o architekturze. Wynik oraz ustrukturyzowany raport, którego programista używa jako punktu wyjścia przegląd przez człowieka, a nie jako substytut.
Podejście nie jest nowe (istnieją narzędzia takie jak CodeClimate, DeepSource, Codacy od lat), ale w 2025 r. jakość modeli sprawi, że rurociąg stanie się opłacalny całkowicie agentyczny. Na przykład Claude Code można zaaranżować w Równoległy podagenci, którzy analizują kod pod różnymi kątami i agregują wyniki w jednym raporcie z przeglądu.
# 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}")
Metryki jakości kodu AI
Testowanie kodu AI wymaga szerszego zestawu metryk niż proste pokrycie kodu. Zasięg mierzy, ile linii kodu wykonują testy, ale nie mierzy jakości z tych testów. Kod AI może mieć 90% pokrycia testami, które niczego nie sprawdzają istotne. Dodatkowe potrzebne wskaźniki to:
Wynik mutacji
Testowanie mutacji to najbardziej niezawodny sposób pomiaru jakości testów i nie tylko
ich ilość. Tester mutacji (np mutant dla Pythona lub
Strykera dla JavaScript/TypeScript) systematycznie wprowadza small
zmiany w kodzie (mutanty: zmiana > in >=, odwraca a
boolean, usuwa return) i sprawdza, czy istniejące testy wykrywają te zmiany.
Jeśli mutant przeżyje testy, testy nie są wystarczająco szczegółowe.
W przypadku kodu AI minimalny wynik mutacji docelowej wynosi 70% (Złoty poziom). Niezależnie od tego wyniki poniżej 50% wskazują na nieodpowiedni zestaw testów pokrycie procentowe.
# 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%)"
Złożoność cyklomatyczna
Złożoność cyklomatyczna mierzy liczbę liniowo niezależnych ścieżek poprzez funkcję. Funkcja o złożoności 1 ma tylko jedną możliwą ścieżkę (bez oddziałów). Funkcja o złożoności 20 ma 20 ścieżek i wymaga co najmniej 20 przypadków testowych zapewniających pełne pokrycie gałęzi. Kod AI ma tendencję do generowania funkcje o dużej złożoności, ponieważ próbuje obsłużyć wszystkie przypadki w jednej funkcji monolityczny.
Zalecane cele dla kodu AI na rok 2025 to: wartość średnia < 10 według funkcji, flaga automatyczna > 15 do obowiązkowego przeglądu, tj automatyczne odrzucenie > 25 jako bramka CI/CD. Wartości te są bardziej rygorystyczne ludzkiego kodu, ponieważ kod sztucznej inteligencji jest bardzo złożony i mniej przewidywalny obsługa błędów w odniesieniu do złożonego kodu napisanego przez programistę.
Najlepsze praktyki: Lista kontrolna dotycząca akceptowania kodu AI
Poniższa lista kontrolna przedstawia minimalną bramkę jakości przed integracją Kod wygenerowany przez sztuczną inteligencję w bazie kodu produkcyjnego. Nie jest to lista wyczerpująca, ale obejmuje najczęstsze kategorie błędów udokumentowane w 2025 r.
Lista kontrolna: Brama jakości dla kodu generowanego przez sztuczną inteligencję
Warstwa 1 — zautomatyzowana (bramka CI/CD, bloker)
- Wszystkie testy jednostkowe poszły pomyślnie (0 niepowodzeń)
- Skanowanie bezpieczeństwa Semgrep: problem o wadze 0 BŁĄD
- ESLint/TypeScript: 0 błędów (ostrzeżenia dopuszczalne z uzasadnieniem)
- Pokrycie kodu >= 80% na nowych liniach
- Brak zależności ze znanym CVE (audyt npm/pip)
- Brak zakodowanych na stałe poświadczeń (git-secrets, Detect-secrets)
Warstwa 2 – Metryki jakości (ostrzeżenie w przypadku nieprzestrzegania)
- Wynik mutacji >= 70% w modułach krytycznych
- Złożoność cyklomatyczna < 15 dla każdej funkcji
- Złożoność poznawcza < 20 dla każdej funkcji
- Brak funkcji zawierających więcej niż 50 linii
- Powielanie kodu < 3%
Warstwa 3 – Przegląd ręczny (obowiązkowy w przypadku istotnych zmian)
- Logika biznesowa zweryfikowana pod kątem oryginalnych specyfikacji
- Poprawiona obsługa błędów: brak cichego przechwytywania, brak ogólnego wyjątku
- Przypadki brzegowe udokumentowane w testach: puste wejście, null, wartości ekstremalne
- Żadnych wywołań zewnętrznych interfejsów API bez przekroczeń limitu czasu i logiki ponawiania
- Mutacja w stanie izolowanym: brak nieudokumentowanych skutków ubocznych
- Testy oparte na właściwościach dla funkcji z wprowadzaniem numerycznym lub tekstowym
Warstwa 4 – Przegląd Architektoniczny (dla nowych modułów lub znaczących refaktoryzacji)
- Przestrzeganie jednej odpowiedzialności (jeden powód zmiany na klasę)
- Zależności od abstrakcji, a nie konkretnych implementacji
- Brak zależności cyklicznych pomiędzy modułami
- Stabilne i dobrze udokumentowane interfejsy publiczne
Anty-wzorzec: czego NIE robić z kodem AI
- Nigdy nie łącz bez przeprowadzenia testów: nawet za „małe poprawki”. Kod AI jest deterministyczny, ale nieprzejrzysty: zmiana monitu ulega zmianie całą realizację w nieoczywisty sposób.
- Nigdy nie ufaj, że „to działa na moim komputerze”: kod AI i szczególnie wrażliwe na różnice środowiskowe. Testowanie CI/CD na kontenerze czystość są obowiązkowe.
- Nigdy nie akceptuj kodu AI w formularzach uwierzytelniających bez SAST: autoryzacja i autoryzacja to obszary, w których kod AI jest statystycznie najbardziej niebezpieczny. Raport Veracode cytuje JWT bez weryfikacji, nieprawidłowego zarządzania sesjami i uprawnień eskalacja wśród 5 głównych luk w zabezpieczeniach AI.
- Nie używaj sztucznej inteligencji do generowania testów kodu AI: testy muszą być napisany przez dewelopera. Jeśli poprosisz sztuczną inteligencję o napisanie własnych testów kod, otrzymasz testy weryfikujące implementację AI, a nie specyfikę systemu.
Wnioski: Testowanie jako czynnik umożliwiający kodowanie Vibe
Celem tego artykułu nie jest straszenie osób używających sztucznej inteligencji do pisania kodu. I odwrotnie: budowanie pewności siebie, aby dobrze ją wykorzystać. 45% bezpieczeństwa wskaźnik awaryjności kodu AI nie oznacza, że pojawia się 45% kodu AI produkcyjne i niebezpieczne. Oznacza to, że bez ustrukturyzowanego procesu weryfikacji, takie jest prawdopodobieństwo. Przy właściwym procesie odsetek ten spada drastycznie.
Połączenie TDD (testy pisane przed, a nie po) z testowaniem opartym na właściwościach Hipoteza, SAST z Semgrep, analiza statyczna z ESLint skonfigurowanym pod wzorce AI, i badanie mutacji w celu sprawdzenia jakości testów, tworzy system ochronny co sprawia, że kodowanie wibracyjne jest profesjonalne. Nie spowalnia rozwoju, lecz go stabilizuje.
Kolejny artykuł z tej serii poświęcony jest Szybka inżynieria dla IDE e Generowanie kodu: jak pisać podpowiedzi, które dają lepszy kod, bezpieczniejsze, łatwiejsze w utrzymaniu i ograniczające dalsze prace testowe.
Zasoby i przydatne linki
Seria kodowania Vibe i rozwoju agentycznego
Cała seria
- Kodowanie Vibe: paradygmat, który zmienił rok 2025
- Kod Claude: Rozwój agenta terminalowego
- Agentyczne przepływy pracy: rozkładanie problemów dla sztucznej inteligencji
- Kodowanie wieloagentowe: LangGraph, CrewAI i AutoGen
- Testowanie kodu wygenerowanego przez sztuczną inteligencję (ten artykuł)
- Szybka inżynieria dla IDE i generowania kodu
- Bezpieczeństwo w kodowaniu Vibe: ryzyko i środki zaradcze
- Przyszłość Agentycznego Rozwoju w 2026 roku
Powiązane spostrzeżenia
- Cursor IDE: pierwsze IDE oparte na sztucznej inteligencji - Jak Cursor radzi sobie z testowaniem kodu AI
- OWASP Top 10 2025: Aktualne luki w zabezpieczeniach - Kontekst luk wprowadzanych przez sztuczną inteligencję
- Claude do przeglądu kodu - Wykorzystaj Claude'a jako agenta recenzującego







