본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 05. 10:11

단위 테스트(Unit Tests)를 작성하는 데 수 시간을 허비하느니, LLM에게 시키고 무엇을 하지 말아야 할지 배웠습니다

요약

반복적인 단위 테스트 작성을 자동화하기 위해 LLM을 활용한 워크플로우를 구축한 경험을 공유합니다. 단순 규칙 기반 생성기의 한계를 극복하고, 소스 코드와 컨텍스트를 LLM에 제공한 뒤 AST 파싱으로 검증하는 방식의 효과를 설명합니다.

핵심 포인트

  • 규칙 기반의 테스트 생성기는 복잡한 도메인 로직과 엣지 케이스 처리에 한계가 있음
  • LLM에 소스 코드와 docstring을 컨텍스트로 제공하여 테스트 생성 품질 향상
  • ast.parse를 활용한 검증 단계가 LLM의 불완전한 코드 출력을 잡는 핵심
  • 부정적 테스트(Negative Test)와 엣지 케이스 생성에 LLM이 매우 유용함

약 한 달 전, 프로젝트를 진행하다가 비즈니스 로직(Business Logic)은 탄탄하고 API 엔드포인트(API Endpoints)는 깔끔하지만, 테스트 파일은 한심할 정도로 빈약한 상태에 도달했습니다. 저는 30개 이상의 유사한 검증 함수를 가지고 있었는데, 각각은 "이 필드가 존재하는가?", "올바른 타입인가?", "이 커스텀 규칙을 통과하는가?"와 같은 질문의 미세한 변형일 뿐이었습니다. 수동으로 작업한다는 것은 동일한 assert 패턴을 수십 번 복사하고, 함수 이름과 테스트 입력값만 바꾸는 것을 의미했습니다. 생각만 해도 뇌가 녹아내리는 기분이었습니다.

저는 테스트의 중요성을 강력히 믿지만, 동시에 지루한 작업을 두 번 반복하지 않는 것 또한 매우 중요하다고 믿습니다. 그래서 테스트 생성(Test Generation)을 자동화할 방법을 찾기 시작했습니다.

처음 시도했던 것 (그리고 왜 실패했는가)

저의 첫 번째 본능은 함수 시그니처(Function Signatures)를 파싱하여 기본적인 assert를 내뱉는 Python 생성기를 작성하는 것이었습니다. 다음과 같은 방식이었죠:

def generate_test(func_name, params):
    lines = [f"def test_{func_name}():"]
    for p in params:
...

이 방식은 아주 사소한 경우에만 작동했습니다. 함수에 부수 효과(Side Effects)가 있거나, 피스처(Fixtures)가 필요하거나, 특정 엣지 케이스(Edge-case) 값이 필요한 순간, 템플릿은 조건문(Conditionals)의 악몽으로 변했습니다. 게다가 에러를 발생시켜야 하는 입력값인 '부정적(Negative)' 테스트는 어떻게 할까요? 제가 만든 생성기는 도메인 로직(Domain Logic)에 대해 아무것도 알지 못했습니다.

다음으로는 정규 표현식(Regular Expressions)을 이용한 규칙 기반(Rule-based) 접근 방식을 시도했습니다. docstring에서 매개변수 타입을 추론하기 위해 약 200줄의 휴리스틱(Heuristics) 코드를 작성했습니다. 한 함수에서는 어느 정도 작동하는 듯했으나, 다음 함수에서는 완전히 망가졌습니다. 아무도 사용하지 않는 언어를 위한 아주 작은 컴파일러(Compiler)를 다시 만들고 있는 듯한 기분이 들었습니다.

실제로 효과가 있었던 접근 방식

저는 적절한 컨텍스트(Context)를 제공한다면 LLM이 더 잘할 수 있을 것이라는 직감이 들었습니다. 아이디어는 간단했습니다. 함수의 소스 코드(docstring 포함)를 언어 모델(Language Model)에 입력하고, pytest 테스트 함수를 생성하도록 요청한 다음, 파일에 쓰기 전에 출력을 검증하는 것이었습니다.

제가 최종적으로 구현한 핵심 루프(Core Loop)는 다음과 같습니다:

import json
import ast
import requests
...

python
{source_code}

"""

    for attempt in range(max_retries):
...

python

검증(Validation) 단계는 매우 중요합니다. LLM은 마크다운 구분 기호(markdown fences), 무작위 주석, 또는 불완전한 괄호를 추가하는 것을 매우 좋아합니다. ast.parse를 사용하여 출력을 파싱함으로써, 테스트 파일에 잘못된 코드를 쓰기 전에 이러한 오류들을 잡아냅니다.

결과 (그리고 놀라운 점들)

저는 세 줄의 로직으로 구성된 validate_email 함수에 이 방식을 적용해 보았습니다. LLM은 다음과 같은 결과를 반환했습니다:

import pytest
from validation import validate_email

...

나쁘지 않았습니다. 심지어 제가 None에 대해 TypeError를 원할 것이라고 추측했는데(실제로 제 함수는 이를 발생시켰습니다), 테스트를 실행했을 때 모두 통과했습니다. 성공이었습니다.

하지만 모든 과정이 순탄했던 것은 아닙니다. 데이터베이스 쿼리가 포함된 복잡한 함수의 경우, LLM은 모킹(mocking)을 잘못 수행한 테스트를 생성했습니다. LLM은 함수가 db.fetch()를 호출할 것이라고 가정했지만, 실제로는 비동기 ORM(async ORM)을 사용하고 있었습니다. 생성된 테스트는 구문(syntactically)적으로는 유효했지만, 의미론적(semantically)으로는 틀렸습니다.

배운 점들

  1. 도메인 특화 로직이 아닌 보일러플레이트(boilerplate) 용도로 LLM을 사용하세요. 만약 함수가 데이터베이스 스키마나 비즈니스 규칙에 대한 깊은 지식을 요구한다면, 생성된 테스트는 너무 일반적일 것입니다. 그런 경우에는 직접 작성하거나 프롬프트에 스키마 컨텍스트를 제공하는 것이 더 낫습니다.

  2. 모델보다 프롬프트 엔지니어링(Prompt engineering)이 더 중요합니다. "프로젝트에 존재하지 않는 import를 포함하지 마세요." 또는 "예외 상황에는 pytest.raises를 사용하세요."와 같은 문구를 추가하는 것만으로도 출력 품질이 극적으로 향상되었습니다.

  3. 항상 출력을 검증하세요. 저는 응답을 ast.parse로 파싱하고, 전체 테스트를 실행하기 전에 생성된 파일에 대해 pytest --collect-only를 빠르게 실행하여 구문 오류나 임포트 오류를 잡아냅니다.

  4. Temperature 0.2 – 0.4가 최적의 지점입니다. 온도가 너무 높으면 무작위 테스트 케이스를 만들어내고, 너무 낮으면 똑같은 패턴을 지루할 정도로 반복합니다.

이 방식을 사용하지 말아야 할 때

  • 외부 서비스(예: AWS, 결제 게이트웨이)에 대한 정밀한 모킹 (Mocking)이 필요한 테스트 스위트(Test suite)인 경우. LLM은 모킹 호출(Mock calls)을 환각 (Hallucination)할 것입니다.
  • 성능이나 동시성 버그 (Concurrency bugs)를 테스트하는 경우. 모델은 타이밍(Timings)이나 레이스 컨디션 (Race conditions)을 이해하지 못합니다.
  • 팀이 매우 일관된 명명 규칙 (Naming conventions)을 중요하게 여기는 경우. LLM은 매번 테스트 이름을 다르게 지을 수 있습니다.

제 검증 함수(Validation functions)들의 경우, 이 방식을 통해 함수당 약 20분을 절약했습니다. 30개의 함수를 기준으로 하면, 10시간을 되찾은 셈입니다. 생성된 테스트가 완벽하지는 않습니다. 저는 여전히 모든 파일을 검토합니다. 하지만 테스트는 많은 버그가 숨어 있는 명백한 부분들을 잡아냅니다.

다음에 바꿀 점

함수 이름 목록을 입력받거나(또는 모듈을 읽어와서) 각 함수에 대한 테스트 파일을 생성한 다음, 제가 청크(Chunks)를 수락하거나 거절할 수 있도록 디프 뷰어 (Diff viewer)를 여는 작은 CLI 도구를 만들 것입니다. 그것이 다음 주말 프로젝트입니다.

이제 궁금합니다: 여러분은 테스트의 지루한 부분들을 어떻게 처리하시나요? 코드 생성 (Code generation)을 사용하시나요, 아니면 그냥 고된 작업을 받아들이시나요? 댓글로 알려주세요. 여러분의 아이디어를 훔치고 싶습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0