11.6.3 PyTest 기반의 자동화된 테스트 하네스(Test Harness) 구현

11.6.3 PyTest 기반의 자동화된 테스트 하네스(Test Harness) 구현

11.6.2절의 파이프라인을 거쳐 수십만 건의 무오류 골든 데이터셋(Golden Dataset) JSONL 파일들을 서버 스토리지에 안전하게 축적해 두었다면, 이제 남은 거대한 엔지니어링 과제는 하나다. 이 막대한 데이터 목록을 메모리에 고속으로 로드(Load)하여 불안정한 메인 챗봇 시스템(SUT, System Under Test)을 집요하게 폭격하고, 돌아오는 응답을 11.5절의 디핑(Diffing) 로직을 통해 쉴 새 없이 채점할 **‘자동화된 테스트 하네스(Automated Test Harness)’**를 견고하게 구현하는 것이다.

현대 AI 엔지니어링과 MLOps 생태계에서, 코루틴(Coroutine) 처리의 압도적 편리함과 파라미터화(Parameterization) 확장성이 가장 뛰어난 Python의 PyTest 프레임워크가 바로 이 핵심 하네스 역할을 가장 완벽하게 수행한다.

1. 데이터 주도 테스팅(Data-Driven Testing) 파라미터화의 진명목

수만 건의 대화 케이스를 단순히 파이썬의 for 루프(Loop) 문 안에 구겨 넣고 LLM을 호출하는 것은 매우 아마추어적인 접근이다. 도중에 단 한 건의 네트워크 타임아웃(Timeout)이나 파싱 에러가 발생해도 스크립트 전체가 크래시(Crash)되며 검증이 강제 종료되기 때문이다. PyTest의 핵심 철학인 @pytest.mark.parametrize 데코레이터(Decorator)를 활용하면 이 거대한 덩어리를 수십만 개의 작고 단단한 독립적인 테스트 박스로 격리(Isolation) 시킬 수 있다.

[PyTest 기반의 챗봇 오라클 검증 하네스 슈도코드]

import pytest
import json
import asyncio
from chatbot_system import LLMChatbotAgent
from oracle_validator import SemanticComparator

# 골든 데이터셋을 발전기(Generator) 형태로 지연 로드(Lazy Loading)
def load_golden_dataset():
    with open("golden_dataset_v3.jsonl", "r") as f:
        return [json.loads(line) for line in f]

@pytest.mark.asyncio
@pytest.mark.parametrize("test_case", load_golden_dataset())
async def test_chatbot_semantic_diffing_and_extraction(test_case):
    # 1. Arrange: 합성 데이터 입력 및 오라클 정답 세팅
    agent = LLMChatbotAgent(model="gpt-4o-production")
    user_chaos_input = test_case["input_chaos_utterance"]
    expected_truth = test_case["oracle_injected_truth"]
    
    # 2. Act: 메인 챗봇 시스템(SUT) 비동기 격발
    actual_response = await agent.chat_async(user_chaos_input)
    
    # 3. Assert (Hard Validation): 디핑 검증기 모듈(11.5절 참조) 호출
    comparator = SemanticComparator()
    
    # 파라미터 추출이 100% 일치하는가?
    assert comparator.compare_args(
        actual_response.parsed_arguments, 
        expected_truth["expected_parsed_arguments"]
    )
    # 최후에 생성된 텍스트 안에 오라클의 숫자가 온전히 보존되었는가?
    assert comparator.verify_semantic_diff(
        expected_truth["expected_deterministic_final_value"], 
        actual_response.natural_language_text
    )

이토록 미니멀하게 추상화된 단 몇십 줄의 코드가 배포 파이프라인의 핵심이다. PyTest 엔진은 이 짤막한 함수를 golden_dataset_v3.jsonl에 들어있는 10만 개의 데이터 레코드 위로 투사하여, 완전히 분리된 10만 개의 독립적인 이진(Binary) 테스트 리포트를 뱉어낸다.

2. 분산 병렬 실행(Parallel Execution)과 무자비한 CI 인그레이션

LLM의 API 네트워크 호출 속도는 일반 DB 쿼리와 달리 호출 당 수백 밀리초에서 수 초에 이르는 극악의 지연 시간(Latency)을 자랑한다. 따라서 10만 개의 테스트를 직렬 연쇄(Sequential)로 순진하게 실행하면 며칠이 고스란히 낭비될 수 있다.

따라서 이 테스트 하네스의 기어봉에는 반드시 pytest-asyncio 코루틴의 힘과 pytest-xdist 분산 플러그인을 결합해야 한다. pytest -n auto 명령어를 통해 단일 서버의 CPU 코어를 최대한으로 혹사시켜 멀티 워커(Worker) 프로세스를 띄우거나, 클라우드의 쿠버네티스(Kubernetes) 노드 수백 대에 이 테스트 덩어리를 쪼개어 분산시킴으로써(Sharding) 거대한 테스트 볼륨을 단 몇 분 내의 초고속(High-speed)으로 회전시켜야 한다.

나아가, 이렇게 가동된 무거운 하네스는 GitHub Actions나 GitLab CI와 같은 파이프라인(CI/CD Pipeline)의 정중앙에 플러그인(Plug-in)되어야 한다. 프롬프트 토큰을 단 한 글자 수정하거나, 파이썬 백엔드 매핑 로직의 사소한 PR(Pull Request)이 발생하여 머지(Merge)되기 직전의 그 찰나에, 이 10만 개의 무자비한 로직이 자동으로 격발되어야 한다.

오직 이 압도적인 규모의 자동화된 테스트 방파제를 에러율(Failure Rate) 0.00%로 뚫고 나온 고결한 코드와 프롬프트 조합만이, 챗봇의 끔찍한 “지능 후퇴(Intelligence Regression)“와 “수학적 환각 발작“을 막아내며 안심하고 엔터프라이즈의 프로덕션 망(Production Net)에 올라갈 자격을 획득하게 된다.