05 - AI 생성 코드 테스트: 품질 전략 및 프레임워크
2025년은 불편한 진실을 확립했습니다. AI가 생성한 코드는 기능적이고, 생성이 빠르며, 종종 구조가 우아합니다. 하지만 안전하지 않습니다. 에 따르면 Veracode GenAI 코드 보안 2025년 보고서, Java, Python, C#의 80개 코딩 작업에 대한 100개 이상의 LLM을 분석했습니다. 그리고 자바스크립트는 AI 생성 코드 중 45%가 보안 테스트에 실패 그리고 소개한다 코드베이스의 OWASP 상위 10개 취약점. 62%에는 명백한 버그가 아니라 오류인 설계 결함이 있습니다. 프로덕션에서만 나타나는 심오한 아키텍처 기능입니다. 그리고 AI가 생성한 코드는 2.74배 더 취약함 인간 개발자가 작성한 코드와 비교됩니다.
이 데이터는 바이브 코딩을 비난하는 것이 아닙니다. 그것들은 다음을 상기시켜줍니다 AI가 변화시킨다 코드를 작성하는 사람이 아니라 테스트할 필요가 없습니다.. 실제로 그것은 그것을 증폭시킵니다. 언제 AI 에이전트가 30초 안에 500줄의 코드를 생성하면 생산성 병목 현상이 완화됩니다. 글쓰기부터 검증까지. AI 코드에 대한 강력한 테스트 전략이 없는 사람 품질이 불투명하고, 버그를 예측할 수 없으며, 안전과 내기.
이 기사는 그러한 전략을 구축합니다. 고유한 오류 패턴을 이해함으로써 단위 테스트, 속성 기반 테스트, 정적 분석, SAST 및 돌연변이 테스트, 수락 또는 거부 시기를 결정하는 운영 체크리스트까지 어시스턴트가 생성한 코드입니다. 대상은 이미 AI 도구를 사용하고 있는 개발자입니다. 업무 흐름을 전문적으로 수행할 수 있다는 자신감을 얻고 싶어 합니다.
무엇을 배울 것인가
- AI가 생성한 코드는 인간의 코드와 다른 방식으로 실패하기 때문입니다.
- 특정 버그 유형: 환각적인 API, 논리 오류, 보안 허점
- pytest 및 가설을 사용한 단위 테스트 및 속성 기반 테스트
- AI 코드용으로 구성된 ESLint 및 SonarQube를 사용한 정적 분석
- Semgrep을 사용한 보안 테스트: AI 패턴에 대한 사용자 정의 규칙
- AI 보조자가 있는 TDD: Red-Green-Refactor 주기가 Red-AI-Green-Review가 됩니다.
- 다중 에이전트 파이프라인을 통한 자동화된 코드 검토
- 품질 지표: 적용 범위, 돌연변이 점수, 순환적 복잡성
- AI 코드 승인 또는 거부를 위한 운영 체크리스트
AI 코드에 특정 테스트가 필요한 이유
인간 개발자가 작성한 코드에는 시스템의 정신적 모델이 포함되어 있습니다. 개발자는 자신이 작성하는 모듈의 위와 아래에 무엇이 있는지 알고 코드를 보았습니다. 생산에 돌입하면 가장자리 주택이 불타는 것을 알고 있습니다. AI에는 이러한 맥락이 없습니다. 오페라 수십억 줄의 코드에서 추출된 통계 패턴을 바탕으로 다음과 같은 솔루션을 생성합니다. 그럴듯한 프롬프트가 주어지면 반드시 그런 것은 아닙니다. 옳은 주어진 시스템.
이러한 차이로 인해 근본적으로 다른 오류 프로필이 생성됩니다. 인간의 코드 예상대로 실패하는 경향이 있습니다. 피곤한 개발자는 잘못된 코드를 복사하고 잊어버립니다. null 검사는 오늘의 모델에 어제의 비즈니스 논리를 사용합니다. 시간 엄수 오류, 추적 가능하며 기존 테스트를 통해 종종 드러납니다. AI 코드는 구조적으로 실패합니다. 절대 일관성을 갖춘 잘못된 API를 구현하고 취약점 패턴을 도입합니다. 수십 가지 기능을 통해 완벽하게 작동하는 아키텍처를 구축합니다. 95%의 경우는 예상할 수 없는 행동으로 나머지 5%는 붕괴됩니다. 이를 위해 설계된 테스트 스위트가 없습니다.
종합적 신뢰도의 문제
AI는 정확성에 관계없이 동일한 신뢰도로 코드를 생성합니다. 에이 존재하지 않는 도서관 기능을 환각하는 모델은 동일한 방식으로 그렇게 합니다. 올바른 알고리즘을 구현한다는 확신을 가지고 있습니다. 불확실성의 징후는 없습니다 생성된 텍스트에서 이로 인해 시각적 검토가 신뢰할 수 없게 됩니다. 것 같다 맞아요 잘 썼으니까요. 실행만이 진실을 드러낸다.
AI 코드의 세 가지 특정 특성에는 다른 테스트 접근 방식이 필요합니다. 기존 테스트에서:
- 상황에 따른 차이: 다른 코드베이스에서 동일한 프롬프트가 생성됩니다. 매우 다른 품질 프로필을 가진 코드. 테스트는 다음에 구체적이어야 합니다. 추상적인 기능뿐만 아니라 배포 컨텍스트도 마찬가지입니다.
- 오류의 내부 일관성: AI가 잘못된 패턴을 도입하는 경우 (예: 사용자 입력의 안전하지 않은 처리) 모든 기능에 걸쳐 이를 복제합니다. 비슷하다. 버그는 고립되어 있지 않고 체계적입니다. 이를 위해서는 다음을 추구하는 테스트가 필요합니다. 단일 사례가 아닌 패턴.
- 오래된 지식: 모델에는 지식 차단이 있습니다. 더 이상 사용되지 않는 API를 사용합니다. 오래된 보안 패턴, 알려진 취약점과의 종속성. 테스트에는 다음이 포함되어야 합니다. 현재 종속성 및 보안 패턴의 검증 계층.
AI 생성 코드의 버그 유형
효과적인 테스트 전략을 구축하기 전에 원하는 것이 무엇인지 알아야 합니다. AI 코드는 고유한 특성을 지닌 별개의 버그 카테고리를 생성합니다. 다양한 탐지 접근법이 필요합니다.
1. 환각 API
가장 교활한 버그이자 가장 많이 문서화되어 있는 버그입니다. AI는 함수, 방법, 매개변수를 발명합니다.
존재하지 않지만 이름이 너무 그럴듯해서 모듈 없이도 시각적 검토를 통과하는 모듈
어려움. 전형적인 예: pandas.DataFrame.filter_by_threshold() 아니
존재하지만 꼭 존재해야 하는 것과 똑같이 들립니다. 코드가 컴파일됩니다(만약
엄격한 유형 검사가 없음), Linting을 통과하고 런타임에 실패합니다.
AttributeError 이는 실제 데이터로 함수가 호출될 때만 나타납니다.
환각에 빠진 API에 대한 주요 방어 수단은 검토가 아닌 실행입니다. 단위 테스트 최소한의 입력으로도 실제로 코드를 호출하는 경우 즉시 감지 이런 종류의 오류. mypy(Python) 또는 TypeScript strict를 사용한 정적 유형 검사 모드는 컴파일 시간에 보호 계층을 추가합니다.
2. 논리 오류 및 Off-by-One
AI 코드는 놀라울 정도로 단순한 논리에 능숙하고 놀라울 정도로 취약합니다. 경계에 논리가 있습니다. 배열 인덱스, 포함 범위와 제외 범위, 조건 루프 종료: AI 코드에서 오류가 발생하는 지점입니다. 표준 테스트가 특별히 설계되지 않은 경우 감지하지 못하는 미묘한 것 테스트 한계 값.
3. 알려진 패턴으로 인한 보안 허점
2025년 Veracode 보고서에는 LLM이 XSS를 86% 방어하지 못한다고 기록되어 있습니다. 관련 사례의 88%에서 로그 주입에 반대합니다. 이는 임의의 버그가 아닙니다. 이는 훈련 데이터에 엄청난 양의 코드가 포함되어 있다는 사실의 결과입니다. 모델이 복제하는 법을 학습했기 때문에 취약합니다. SQL 주입, SSRF, 경로 탐색, 부적절한 역직렬화: 테스트뿐만 아니라 전용 SAST가 필요한 체계적인 패턴 기능적.
4. 건축 설계 결함
자동화된 테스트로 감지하기가 가장 어렵습니다. AI는 효과적인 솔루션을 구축합니다. 로컬이지만 확장할 수 없는 사람, 올바른 인터페이스를 갖고 있지만 책임이 잘못된 사람, 필수 계약을 준수하지만 기본 아키텍처 원칙을 위반하는 경우 (단일 책임, 관심사 분리, 느슨한 결합) 62% 디자인 결함 Veracode가 인용한 내용이 이 범주에 속합니다. 탐지에는 다음의 조합이 필요합니다. 복잡성 측정, 종속성 분석 및 구조화된 코드 검토.
AI 코드를 위한 테스트 프레임워크
단위 테스트 전략: 경계 테스트
AI 코드의 단위 테스트 전략은 가장자리 값으로 이동해야 합니다. 그리고 불분명한 경우. AI 코드는 Happy Path 사례를 잘 처리하고 훈련을 받았습니다. 수백만 개의 행복한 경로 사례 중). 경계, 비정상적인 입력, 오류 처리에 관한 것입니다.
AI 코드에 대한 효과적인 단위 테스트 모음에는 다음이 포함됩니다.
- 유효한 범위(0, 음수, max-int 값)의 가장자리에 있는 입력으로 테스트합니다.
- 빈 입력, null, 없음, 빈 문자열로 테스트
- 특수 문자나 예상치 못한 인코딩이 포함된 입력으로 테스트합니다.
- 오류 발생 시 동작을 확인하는 테스트(예외 처리)
- 부작용(공유 상태, 데이터베이스, 파일 시스템 변경)을 확인하는 테스트
# 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 생태계 전반에 걸쳐) 생성하는 에이전트 Claude Code를 시연했습니다. 가설 테스트는 수동으로 작성된 테스트와 비교하여 다르고 보완적인 버그를 찾습니다. 오탐(false positive)이 거의 없습니다. 가설이 기반을 두고 있기 때문에 이 접근 방식은 효과적입니다. 테스트 영역을 아는 클로드 코드.
# 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(Static Application Security Testing)가 한 단계 더 나아갑니다. 스타일 린팅: 종종 눈에 보이지 않는 활성 취약점 패턴을 찾습니다. 구문상 정확하기 때문에 수동으로 검토합니다. 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 코드용 다중 에이전트 코드 검토 파이프라인 품질의 다양한 측면에서 동시에 작동하는 보안 에이전트 하나는 코드 스타일에 관한 것, 하나는 성능에 관한 것, 하나는 아키텍처에 관한 것입니다. 결과 개발자가 시작점으로 사용하는 구조화된 보고서 대체 수단이 아닌 사람의 검토를 받습니다.
접근 방식은 새로운 것이 아닙니다(CodeClimate, DeepSource, Codacy와 같은 도구가 있음). 수년 동안), 그러나 2025년에는 모델의 품질로 인해 파이프라인이 실행 가능해졌습니다. 완전히 에이전트입니다. 예를 들어 Claude Code는 다음과 같이 조정될 수 있습니다. 다양한 각도에서 코드를 분석하고 집계하는 병렬 하위 에이전트 결과를 단일 검토 보고서로 제공합니다.
# 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의 경우 또는
스트라이커 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인 함수에는 가능한 경로가 하나만 있습니다. (가지 없음). 복잡도가 20인 함수에는 20개의 경로가 있으며 최소한 완전한 브랜치 적용을 위한 20개의 테스트 케이스. AI 코드는 다음을 생성하는 경향이 있습니다. 단일 함수에서 모든 경우를 처리하려고 하기 때문에 복잡도가 높은 함수 모 놀리 식.
2025년 AI 코드의 권장 목표는 다음과 같습니다. 중앙값 < 10 기능별, 자동 플래그 > 15 필수 검토의 경우 e 자동 거부 > 25 CI/CD 게이트로. 이 값은 더 엄격합니다. 왜냐하면 AI 코드는 매우 복잡하고 예측 가능성이 낮기 때문입니다. 개발자가 작성한 복잡한 코드에 대한 오류 처리.
모범 사례: AI 코드 승인을 위한 체크리스트
다음 체크리스트는 통합 전 최소 품질 게이트를 나타냅니다. 프로덕션 코드베이스의 AI 생성 코드. 완전한 목록은 아니지만, 2025년에 문서화된 가장 빈번한 오류 카테고리를 다룹니다.
체크리스트: AI 생성 코드의 품질 게이트
레이어 1 - 자동화(CI/CD Gate, 차단기)
- 모든 단위 테스트 통과(실패 0개)
- 보안 검사 Semgrep: 심각도 0 문제 ERROR
- ESLint/TypeScript: 0 오류(정의가 있는 경우 경고가 허용됨)
- 코드 적용 범위 >= 새 줄의 80%
- 알려진 CVE(npm/pip 감사)와의 종속성 없음
- 하드코딩된 자격 증명 없음(git-secrets, discover-secrets)
레이어 2 - 품질 측정항목(준수되지 않는 경우 경고)
- 중요 모듈의 돌연변이 점수 >= 70%
- 각 기능에 대한 순환 복잡도 < 15
- 각 기능에 대한 인지 복잡성 < 20
- 50줄을 초과하는 기능은 없습니다.
- 코드 중복 < 3%
레이어 3 - 수동 검토(중요한 변경의 경우 필수)
- 원래 사양과 비교하여 검증된 비즈니스 로직
- 수정된 오류 처리: 자동 포착 없음, 일반 예외 없음
- 테스트에 문서화된 극단적 사례: 빈 입력, null, 극단값
- 시간 초과 및 재시도 논리 없이 외부 API를 호출할 수 없습니다.
- 고립된 상태 돌연변이: 문서화되지 않은 부작용 없음
- 숫자 또는 텍스트 입력이 포함된 함수에 대한 속성 기반 테스트
레이어 4 - 아키텍처 검토(새 모듈 또는 중요한 리팩토링용)
- 단일 책임 존중(클래스당 변경 사유 1개)
- 구체적인 구현이 아닌 추상화에 대한 종속성
- 모듈 간 순환 종속성이 없습니다.
- 안정적이고 잘 문서화된 공개 인터페이스
안티 패턴: AI 코드로 하지 말아야 할 것
- 테스트를 실행하지 않고 병합하지 마세요. "작은 수정"의 경우에도 마찬가지입니다. AI 코드는 결정적이지만 투명하지 않습니다. 프롬프트 변경 명확하지 않은 방식으로 전체 구현을 수행합니다.
- "내 컴퓨터에서 작동한다"는 말을 절대 믿지 마세요. AI 코드 특히 환경의 차이에 민감합니다. 컨테이너에서 CI/CD 테스트 청소는 필수입니다.
- SAST 없이 인증 양식에 AI 코드를 허용하지 마십시오. 인증 및 승인은 AI 코드가 통계적으로 가장 위험한 영역입니다. Veracode 보고서는 검증되지 않은 JWT, 잘못된 세션 관리 및 권한을 인용합니다. 상위 5개 AI 취약점 중 상위 5개 취약점이 확대되었습니다.
- AI 코드에 대한 테스트를 생성하기 위해 AI를 사용하지 마세요. 테스트는 다음과 같아야합니다 개발자가 작성했습니다. AI에게 자체 테스트 작성을 요청하면 코드를 사용하면 시스템 세부 사항이 아닌 AI 구현을 확인하는 테스트를 받게 됩니다.
결론: Vibe 코딩을 가능하게 하는 테스트
이 기사의 목표는 AI를 사용하여 코드를 작성하는 사람들에게 겁을 주는 것이 아닙니다. 그리고 그 반대는 그것을 잘 사용할 수 있다는 자신감을 키우는 것입니다. 45% 보안 AI 코드의 실패율이 들어오는 AI 코드의 45%를 의미하는 것은 아닙니다. 생산 및 안전하지 않습니다. 체계화된 검증과정이 없다는 뜻이다. 그것이 확률입니다. 올바른 프로세스를 사용하면 해당 비율이 감소합니다. 과감하게.
TDD(이후가 아닌 이전에 작성된 테스트)와 속성 기반 테스트의 조합 가설, Semgrep을 사용한 SAST, AI 패턴에 대해 구성된 ESLint를 사용한 정적 분석, 및 돌연변이 테스트를 통해 테스트 품질을 검증하고 보호 시스템을 구축합니다. 바이브 코딩을 전문적으로 만들어줍니다. 개발 속도를 늦추는 것이 아니라 안정화시킵니다.
시리즈의 다음 기사에서는 IDE e를 위한 신속한 엔지니어링 코드 생성: 더 나은 코드를 생성하는 프롬프트를 작성하는 방법, 더 안전하고 유지 관리가 용이하며 다운스트림 테스트 작업이 줄어듭니다.
리소스 및 유용한 링크
Vibe Coding 및 Agentic 개발 시리즈
전체 시리즈
관련 통찰력
- 커서 IDE: 최초의 AI 우선 IDE - 커서가 AI 코드 테스트를 처리하는 방법
- OWASP 2025년 상위 10개: 현재 취약점 - AI로 인해 발생하는 취약점의 맥락
- 코드 검토를 위한 클로드 - 클로드를 리뷰 대리인으로 활용







