AI가 테스트 작성을 바꾸는 방법
개발자가 가장 싫어하는 작업 중 하나가 테스트 작성입니다. LLM은 코드를 분석해 엣지 케이스를 찾고 테스트를 자동 생성할 수 있습니다.
flowchart LR Code[소스 코드] --> LLM[LLM 분석] LLM --> Unit[단위 테스트] LLM --> Edge[엣지 케이스] LLM --> Mock[Mock 코드] Unit --> Coverage[커버리지 리포트] Edge --> Coverage Mock --> Coverage
pytest 테스트 자동 생성
import anthropic
import ast
import inspect
client = anthropic.Anthropic()
def generate_pytest_tests(source_code: str, module_name: str) -> str:
response = client.messages.create(
model="claude-sonnet-4-6-20251001",
max_tokens=4096,
system='''당신은 Python 테스트 전문가입니다.
주어진 코드에 대해 pytest 테스트를 작성하세요.
규칙:
- 모든 public 함수/메서드 테스트
- 정상 케이스 + 엣지 케이스 + 예외 케이스 포함
- fixture 적극 활용
- parametrize로 다양한 입력값 테스트
- 테스트 이름은 test_함수명_상황 형식
- Mock은 pytest-mock 사용''',
messages=[{
"role": "user",
"content": f"다음 Python 코드에 대한 pytest 테스트를 작성해주세요:
{source_code}
모듈명: {module_name}"
}]
)
return response.content[0].text
# 사용
with open("src/user_service.py") as f:
source = f.read()
tests = generate_pytest_tests(source, "user_service")
with open("tests/test_user_service.py", "w") as f:
f.write(tests)
print("테스트 파일 생성 완료")
실제 생성 예시입력 코드:
def calculate_discount(price: float, discount_pct: float) -> float:
if discount_pct < 0 or discount_pct > 100:
raise ValueError("할인율은 0-100 사이여야 합니다")
return price * (1 - discount_pct / 100)
AI 생성 테스트:
import pytest
from pricing import calculate_discount
class TestCalculateDiscount:
@pytest.mark.parametrize("price,pct,expected", [
(10000, 10, 9000.0),
(50000, 50, 25000.0),
(100, 100, 0.0),
(100, 0, 100.0),
])
def test_normal_cases(self, price, pct, expected):
assert calculate_discount(price, pct) == expected
@pytest.mark.parametrize("invalid_pct", [-1, 101, -100, 200])
def test_invalid_discount_raises(self, invalid_pct):
with pytest.raises(ValueError, match="할인율은 0-100"):
calculate_discount(10000, invalid_pct)
def test_zero_price(self):
assert calculate_discount(0, 50) == 0.0
def test_float_precision(self):
result = calculate_discount(1.0, 33.33)
assert abs(result - 0.6667) < 0.001
JavaScript/TypeScript 테스트 생성
def generate_jest_tests(ts_source: str) -> str:
response = client.messages.create(
model="claude-sonnet-4-6-20251001",
max_tokens=4096,
system='''TypeScript/Jest 테스트 전문가로서:
- describe/it 구조 사용
- beforeEach/afterEach로 설정
- jest.mock()으로 의존성 모킹
- async/await 패턴
- expect(fn).toThrow() 예외 테스트
- 타입 안전성 고려''',
messages=[{"role": "user", "content": f"Jest 테스트 작성:
```typescript
{ts_source}
```"}]
)
return response.content[0].text
Playwright
E2E 테스트 자동 생성
def generate_playwright_tests(page_description: str, user_flows: list[str]) -> str:
flows_text = "
".join(f"- {flow}" for flow in user_flows)
response = client.messages.create(
model="claude-sonnet-4-6-20251001",
max_tokens=4096,
system='''Playwright E2E 테스트 전문가:
- page.locator() 사용 (XPath 지양)
- await expect() 단언문
- 스크린샷으로 시각적 검증
- 네트워크 요청 인터셉트
- 모바일/데스크톱 뷰포트 테스트''',
messages=[{
"role": "user",
"content": f'''페이지: {page_description}
테스트할 사용자 흐름:
{flows_text}
Playwright TypeScript 테스트를 작성해주세요.'''
}]
)
return response.content[0].text
# 로그인 페이지 테스트 생성
tests = generate_playwright_tests(
page_description="이메일/비밀번호 로그인 페이지",
user_flows=[
"정상 로그인 후 대시보드 이동",
"잘못된 비밀번호 입력 시 에러 메시지",
"이메일 미입력 시 필드 검증",
"비밀번호 찾기 링크 클릭",
]
)
커버리지 기반 테스트 보강
import subprocess
import re
def find_uncovered_lines(source_file: str) -> list[int]:
# pytest-cov 실행
result = subprocess.run(
["pytest", "--cov", source_file.replace(".py", ""),
"--cov-report", "term-missing", "-q"],
capture_output=True, text=True
)
# 미커버 라인 파싱
match = re.search(r'TOTAL.*?(\d+)%.*?Missing: ([\d, ]+)', result.stdout)
if match:
missing_lines = [int(l) for l in match.group(2).split(', ')]
return missing_lines
return []
def generate_tests_for_uncovered(source_file: str, missing_lines: list[int]) -> str:
with open(source_file) as f:
lines = f.readlines()
uncovered_code = ''.join(lines[max(0, l-3):l+3] for l in missing_lines[:10])
response = client.messages.create(
model="claude-sonnet-4-6-20251001",
max_tokens=2048,
messages=[{
"role": "user",
"content": f"다음 미커버 코드 라인에 대한 테스트를 추가해줘:
{uncovered_code}"
}]
)
return response.content[0].text
뮤테이션 테스트로 테스트 품질 검증
# mutmut으로 뮤테이션 테스트
pip install mutmut
mutmut run --paths-to-mutate src/
# 살아남은 뮤턴트 확인
mutmut results
# AI에게 살아남은 뮤턴트 제거 요청
mutmut show 5 # 5번 뮤턴트 상세 보기
def kill_surviving_mutants(mutant_diff: str) -> str:
response = client.messages.create(
model="claude-sonnet-4-6-20251001",
max_tokens=1024,
messages=[{
"role": "user",
"content": f'''이 뮤턴트를 잡는 테스트를 작성해줘:
뮤턴트 diff:
{mutant_diff}'''
}]
)
return response.content[0].text
CI/CD 파이프라인에 AI 테스트 생성을 포함하면 신규 기능 추가 시 자동으로 테스트 스켈레톤이 생성됩니다.





