
pytest에서 monkeypatching 없이 OpenAI 및 Anthropic API 호출을 모킹(mock)하는 방법
요약
LLM API 테스트 시 발생하는 비용, 속도, 불안정성 문제를 해결하기 위해 monkeypatching 대신 record/replay 패턴을 사용하는 방법을 소개합니다. llm-mock 플러그인을 활용하여 실제 API 응답을 기록하고 이를 재사용하는 효율적인 테스트 전략을 제안합니다.
핵심 포인트
- 실제 API 호출을 기록(Record)하고 재생(Replay)하여 테스트 비용과 속도 문제 해결
- llm-mock 플러그인을 통해 OpenAI 및 Anthropic SDK의 HTTP 호출을 가로챔
- 프로덕션 코드 수정 없이 결정론적이고 안정적인 CI/CD 환경 구축 가능
- 저장된 JSON 피스처를 Git에 커밋하여 오프라인 테스트 환경 구현
LLM API를 사용하여 개발하고 있다면, 아마 다음과 같은 문제에 직면했을 것입니다: 테스트 스위트가 실제 API 호출을 수행한다는 점입니다.
그것은 다음을 의미합니다:
- 모든 CI 실행 시 비용이 발생함
- 테스트가 느림 (호출당 1~3초)
- 테스트가 불안정함 (LLM 출력은 결정론적(deterministic)이지 않음)
- API 키가 없으면 테스트가 실패함
일반적인 해결책은 monkeypatching입니다. 즉, 클라이언트를 가짜(fake)로 교체하는 것이죠. 하지만 이는 가짜 응답을 수동으로 유지 관리해야 함을 의미하며, 테스트가 실제 API가 반환하는 내용을 제대로 반영하지 못하게 됩니다.
더 나은 접근 방식이 있습니다: 실제 응답을 한 번 기록하고, 이를 영구적으로 재생(replay)하는 것입니다.
record/replay 패턴
API를 가짜로 만드는 대신, 이를 가로챕니다(intercept):
- Record mode (기록 모드) — 실제 API를 대상으로 한 번 실행하고, 응답을 JSON 파일로 저장합니다.
- Replay mode (재생 모드) — 네트워크 호출 없이 저장된 응답을 즉시 반환합니다.
fixture 파일은 git에 커밋됩니다. CI는 오프라인으로 실행됩니다. 테스트는 결정론적이며 비용이 들지 않습니다.
llm-mock
https://github.com/autopost/llm-mock은 Anthropic 및 OpenAI SDK를 위해 정확히 이 작업을 수행하는 pytest 플러그인입니다. 이는 HTTP 계층에서 가로채기 때문에, 여러분의 프로덕션 코드는 전혀 수정되지 않습니다.
pip install llm-mock
예시: Anthropic 파이프라인 테스트하기
다음과 같은 프로덕션 코드가 있다고 가정해 봅시다:
# my_app/pipeline.py
import anthropic
...
1단계 — 한 번 기록하기
API 키를 사용하여 로컬에서 다음을 실행합니다:
from llm_mock import llm_mock
from my_app.pipeline import summarize
...
ANTHROPIC_API_KEY=sk-... python record_fixtures.py
이렇게 하면 tests/fixtures/summarize.json 파일이 생성됩니다. 이를 git에 커밋하세요.
2단계 — 테스트에서 재생하기
# tests/test_pipeline.py
import pytest
from my_app.pipeline import summarize
...
pytest # API 키가 필요 없으며, 즉각적이고 결정론적입니다
그게 전부입니다. 이 데코레이터(decorator)는 Anthropic SDK가 내부적으로 수행하는 httpx 호출을 가로채서 저장된 응답을 반환합니다.
자동 모드 (Auto mode) — 가장 쉬운 워크플로우
기록(record)과 재생(replay)을 전혀 고민하고 싶지 않다면, mode="auto"를 사용하세요:
@pytest.mark.llm_replay(fixture="summarize", mode="auto")
def test_summarize():
result = summarize("Long article about climate change...")
...
- Fixture가 존재하면 → 이를 재생(replay)합니다.
- Fixture가 없으면 → 자동으로 기록(record)합니다.
첫 실행에는 API 키가 필요합니다. 그 이후의 모든 실행은 무료이며 오프라인 상태에서 진행됩니다.
Fixture 새로고침하기
프롬프트(prompt)를 변경하거나 모델을 업데이트할 때, 테스트 코드를 수정하지 않고도 fixture를 새로고침할 수 있습니다:
LLM_MOCK_DISABLED=1 ANTHROPIC_API_KEY=sk-... pytest
LLM_MOCK_DISABLED=1은 llm-mock을 완전히 우회합니다. 즉, 모든 테스트가 실제 API를 호출하고 새로운 응답을 저장합니다.
Fixture의 형태
일반적인 JSON 형식으로, 사람이 읽기 쉽고 PR(Pull Request)에서 차이점(diff)을 확인하기 용이합니다:
{
"version": "1.0",
"provider": "anthropic",
...
요청(Requests)은 (model, messages, temperature)의 SHA256 해시값으로 매칭됩니다. 따라서 동일한 요청은 항상 동일한 fixture 항목에 도달합니다.
OpenAI에서도 작동합니다
@pytest.mark.llm_replay(fixture="gpt_summary", mode="auto")
def test_openai_summary():
result = my_openai_pipeline("Summarize this...")
...
provider="all"(기본값)을 사용하면 하나의 fixture 파일에 두 제공업체(provider)를 모두 포함할 수 있습니다.
다른 접근 방식과의 비교
| 접근 방식 (Approach) | 문제점 (Problem) |
|---|---|
| unittest.mock / monkeypatch | 가짜 응답 (Fake responses)이 실제 API 동작과 괴리됨 |
| ... |
설치 (Install)
pip install ll-mock
저장소 및 전체 문서: https://github.com/autopost/llm-mock
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기