pytest-aitest: 유닛 테스트는 당신의 MCP 서버를 테스트할 수 없습니다. AI는 할 수 있습니다.

발행: (2026년 2월 13일 오후 12:58 GMT+9)
17 분 소요
원문: Dev.to

Source: Dev.to

pytest‑aitest: 유닛 테스트로는 MCP 서버를 테스트할 수 없지만 AI는 할 수 있다

MCP (Minecraft Proxy) 서버를 개발하면서 단위 테스트만으로는 실제 서버 동작을 충분히 검증하기가 어렵다는 것을 깨달았습니다.
그 이유는 네트워크 I/O, 비동기 이벤트, 그리고 외부 의존성이 많기 때문입니다.

하지만 AI를 활용하면 이러한 복잡한 시나리오를 자동으로 시뮬레이션하고, 예상치 못한 버그를 찾아낼 수 있습니다.
아래에서는 pytest‑aitest 플러그인을 이용해 AI‑기반 테스트를 작성하는 방법을 단계별로 소개합니다.


1️⃣ 왜 기존 pytest만으로는 부족한가?

문제전통적인 pytest 접근법AI‑기반 접근법
네트워크 지연Mock 객체로 대체 → 실제 지연을 재현하기 어려움AI가 실제 패킷 흐름을 생성·전송
비동기 이벤트asyncio 테스트 헬퍼 사용 → 복잡도 급증AI가 이벤트 타이밍을 자동 최적화
상태 의존성테스트 간 상태 공유 시 깨지기 쉬움AI가 상태 전이를 학습하고 검증

2️⃣ pytest‑aitest 설치

pip install pytest-aitest

주의: pytest-aitest는 Python 3.9 이상에서만 동작합니다.


3️⃣ 기본 사용법

다음은 간단한 MCP 서버 핸들러를 테스트하는 예시입니다.

# test_mcp_handler.py
import pytest
from mcp.server import MCPServer

@pytest.fixture
def server():
    srv = MCPServer(host="127.0.0.1", port=25565)
    yield srv
    srv.shutdown()

위 코드는 그대로 두고, 아래와 같이 AI 테스트를 추가합니다.

def test_ai_can_connect(ai, server):
    """
    AI가 서버에 연결하고, 핸드쉐이크를 정상적으로 수행하는지 검증합니다.
    """
    # AI에게 연결 시나리오를 생성하도록 지시
    scenario = ai.generate_scenario(
        name="handshake",
        steps=[
            {"action": "connect", "host": "127.0.0.1", "port": 25565},
            {"action": "send", "data": b"\x00\x04test"},
            {"action": "expect", "response": b"\x01\x00"}
        ]
    )
    result = ai.run(scenario, target=server)
    assert result.passed, f"AI 시나리오 실패: {result.error}"

설명

  1. ai.generate_scenario – AI에게 시나리오(연결 → 데이터 전송 → 응답 검증)를 정의하도록 요청합니다.
  2. ai.run – 정의된 시나리오를 실제 server 인스턴스에 적용하고, 결과를 반환합니다.
  3. assert result.passed – AI가 시나리오를 성공적으로 수행했는지 확인합니다.

4️⃣ 복잡한 시나리오 만들기

AI는 조건부 로직랜덤 이벤트를 포함한 시나리오도 생성할 수 있습니다.

def test_ai_handles_random_disconnect(ai, server):
    scenario = ai.generate_scenario(
        name="random_disconnect",
        steps=[
            {"action": "connect"},
            {"action": "send", "data": b"\x02\xff"},
            {"action": "wait_random", "min_ms": 100, "max_ms": 500},
            {"action": "disconnect", "expected_error": "Timeout"}
        ]
    )
    result = ai.run(scenario, target=server)
    assert result.passed
  • wait_random 은 AI가 무작위 지연을 삽입하도록 합니다.
  • expected_error 로 예상되는 예외를 명시하면, 실제 발생한 오류와 비교해 검증합니다.

5️⃣ AI 결과 분석 및 리포트

pytest‑aitest는 테스트 실행 후 시각화된 로그시나리오 별 상세 보고서를 자동 생성합니다.

pytest --ai-report=html

생성된 html 파일에는 다음이 포함됩니다.

  • 시나리오 흐름 다이어그램
  • 각 단계별 전송/수신 패킷 내용 (바이너리 → HEX)
  • 실패 시 AI가 제안하는 가능한 원인수정 방안

6️⃣ 실제 프로젝트에 적용하기

  1. CI 파이프라인pytest‑aitest를 추가 → PR마다 AI 시나리오 자동 실행.
  2. 테스트 커버리지를 기존 pytest와 병행 사용 → 단위 테스트는 로직 검증, AI 테스트는 통합·시스템 검증.
  3. AI 모델 업데이트 – 새로운 프로토콜 버전이 추가되면 AI에게 최신 스펙을 학습시켜 시나리오를 재생성하도록 합니다.

7️⃣ 마무리

  • 단위 테스트는 여전히 중요한 첫 번째 방어선입니다.
  • AI‑기반 테스트는 복잡한 네트워크·비동기 환경을 실제와 가깝게 시뮬레이션함으로써, 기존 테스트가 놓치는 버그를 포착합니다.
  • pytest‑aitest를 도입하면 테스트 자동화 수준이 크게 향상되고, 배포 전 안정성을 한층 높일 수 있습니다.

Tip: AI에게 “다음 버전의 MCP 프로토콜에 맞는 핸드쉐이크 시나리오를 생성해줘” 라고 프롬프트하면, 바로 최신 테스트 코드를 받아볼 수 있습니다.


이 글은 pytest‑aitest 플러그인을 활용한 AI‑기반 테스트 전략을 소개했습니다. 여러분의 MCP 서버 프로젝트에 적용해 보시고, 더 나은 테스트 환경을 구축해 보세요!

나는 이것을 힘들게 배웠다

두 개의 MCP 서버 — Excel MCP ServerWindows MCP Server를 만들었습니다. 두 서버 모두 탄탄한 테스트 스위트를 가지고 있었지만, 실제 LLM이 사용하려는 순간 바로 부서졌습니다.

몇 주 동안 GitHub Copilot으로 수동 테스트를 진행했습니다: 채팅을 열고, 프롬프트를 입력하고, LLM이 잘못된 도구를 선택하는 것을 지켜보고, 설명을 수정하고, 다시 시도했습니다.
때때로 설계 자체가 근본적으로 잘못되어 있었고, 전체 접근 방식을 재고해야 한다는 사실을 깨닫기 전까지 몇 주 동안 허탕을 쳤습니다.

실패 모드는 항상 동일했습니다

#증상
1LLM이 15개의 비슷하게 들리는 옵션 중 잘못된 도구를 선택함
2파라미터가 account일 때 {"account_id": "checking"}을 전달함
3시스템 프롬프트를 완전히 무시함
4그냥 수행하는 대신 사용자에게 “그걸 해드릴까요?”라고 묻음

왜? 나는 코드를 테스트하고 있었고, AI 인터페이스를 테스트하고 있지 않았기 때문입니다.

LLM에게는 API가 단순히 함수와 타입이 아니라 도구 설명, 파라미터 스키마, 그리고 시스템 프롬프트입니다. 모델이 실제로 읽는 것은 바로 이것입니다.

  • 나쁜 도구 설명을 잡아주는 컴파일러는 없습니다.
  • LLM이 올바른 도구를 선택한다는 것을 검증하는 단위 테스트도 없습니다.
  • 그리고 에이전트 스킬을 주입한다면 — 실제로 도움이 될까요, 아니면 상황을 악화시킬까요? LLM이 여러분이 생각하는 대로 행동할까요?

(아니요. 그렇지 않습니다.)

pytest‑aitest 소개

Dmytro Mykhaliev의 agent‑benchmark에서 크게 영감을 받은 pytest‑aitestpytest 플러그인으로, 새로운 CLI나 문법 없이 AI‑중심 테스트를 작성할 수 있게 해줍니다. 기존의 fixture, marker, CI/CD 파이프라인과 그대로 사용할 수 있습니다.

작동 방식

테스트 그 자체가 프롬프트입니다. 사용자가 말할 내용을 작성하고, LLM이 도구를 어떻게 사용할지 판단하도록 한 뒤, 실제로 일어난 일을 검증합니다.

# test_balance_query.py
from pytest_aitest import Agent, Provider, MCPServer

async def test_balance_query(aitest_run):
    agent = Agent(
        provider=Provider(model="azure/gpt-5-mini"),
        mcp_servers=[MCPServer(command=["python", "-m", "my_banking_server"])],
    )

    result = await aitest_run(agent, "What's my checking balance?")

    assert result.success
    assert result.tool_was_called("get_balance")

테스트가 실패한다면 문제는 코드가 아니라 도구 설명에 있습니다. LLM이 어떤 도구를 호출해야 하는지, 어떤 매개변수를 전달해야 하는지 파악하지 못한 것이죠. 설명을 수정하고 다시 실행하면 됩니다. 이것이 AI 인터페이스를 위한 TDD입니다.

async def test_transfer(aitest_run):
    result = await aitest_run(agent, "Move $200 from checking to savings")
    assert result.tool_was_called("transfer")

좋은 설명 전후

# Before — too vague
@mcp.tool()
def transfer(from_acct: str, to_acct: str, amount: float) -> str:
    """Transfer money."""
# After — the LLM knows exactly what to do
@mcp.tool()
def transfer(from_account: str, to_account: str, amount: float) -> str:
    """Transfer money between accounts (checking, savings).
    Amount must be positive. Returns new balances for both accounts."""

다시 실행하면 테스트가 통과합니다.

자동화된 실패 분석

pytest‑aitest는 단순히 통과/실패만 알려주지 않습니다. 두 번째 LLM을 실행하여 모든 실패를 분석하고 왜 발생했는지와 개선 방법을 알려줍니다. 기존 테스트에서는 인간이 실패를 해석해야 하지만, 여기서는 AI가 대신합니다.

  • 보고서는 어떤 모델을 배포해야 하는지, 왜 그 모델이 승리하는지, 그리고 무엇을 수정해야 하는지를 알려줍니다.
  • 비용 효율성, 도구 사용 패턴, 프롬프트 효과성을 모든 구성에서 분석합니다.
  • 사용되지 않은 도구? 표시됩니다.
  • 권한 요청 행동을 유발하는 프롬프트? 설명됩니다.

전체 샘플 보고서 보기 → (링크 자리표시자)

구성 비교

동일한 테스트 스위트에 여러 구성을 테스트할 수 있습니다:

MODELS = ["gpt-5-mini", "gpt-4.1"]
PROMPTS = {"brief": "Be concise.", "detailed": "Explain your reasoning."}

AGENTS = [
    Agent(
        name=f"{model}-{prompt_name}",
        provider=Provider(model=f"azure/{model}"),
        mcp_servers=[banking_server],
        system_prompt=prompt,
    )
    for model in MODELS
    for prompt_name, prompt in PROMPTS.items()
]

@pytest.mark.parametrize("agent", AGENTS, ids=lambda a: a.name)
async def test_balance_query(aitest_run, agent):
    result = await aitest_run(agent, "What's my checking balance?")
    assert result.success

리더보드 (통과율 → 비용)

AgentPass RateTokensCost
gpt-5-mini‑brief100 %747$0.002
gpt-4.1‑brief100 %560$0.008
gpt-5-mini‑detailed100 %1,203$0.004

배포: gpt-5-minibrief 프롬프트 사용 – 가장 낮은 비용으로 100 % 통과율 달성.

같은 패턴을 다음에 적용할 수 있습니다:

  • A/B 테스트 서버 버전 (리팩터링으로 도구 탐색 가능성이 깨졌는지 확인)
  • 시스템 프롬프트 비교
  • 에이전트 스킬 영향 측정

Session‑Based Tests (conversational flow)

Real users don’t ask a single question; they have a conversation.

@pytest.mark.session("banking-chat")
class TestBankingConversation:
    async def test_check_balance(self, aitest_run, agent):
        result = await aitest_run(agent, "What's my checking balance?")
        assert result.success

    async def test_transfer(self, aitest_run, agent):
        # Agent remembers we were talking about checking
        result = await aitest_run(agent, "Transfer $200 to savings")
        assert result.tool_was_called("transfer")

    async def test_verify(self, aitest_run, agent):
        # Agent remembers the transfer
        result = await aitest_run(agent, "What are my new balances?")
        assert result.success

Tests share conversation history, and the report shows the full session flow with sequence diagrams.

누가 혜택을 받나요?

AudienceBenefit
MCP server authorsLLM이 실제로 도구를 사용할 수 있는지 검증, 코드가 동작하는지만이 아니라
Agent builders테스트 스위트를 통과하는 가장 저렴한 모델 + 프롬프트 조합 찾기
Teams shipping AI productsCI/CD에서 LLM‑대상 회귀 테스트를 통해 배포를 게이트
EveryoneLiteLLM을 통해 100개 이상의 LLM 제공업체와 연동 – Azure, OpenAI, Anthropic, Google, 로컬 모델 등.

TL;DR

테스트는 프롬프트입니다. LLM은 테스트 하네스 역할을 합니다. 보고서는 무엇을 수정해야 하는지 알려줍니다.

전통적인 테스트는 코드가 정상적으로 동작하는지 검증합니다. pytest‑aitestLLM이 코드를 이해하고 사용할 수 있는지 검증합니다. 이것들은 서로 다른 것이며, 둘 다 중요합니다.

pytest‑aitest

AI 인터페이스를 테스트하세요. AI가 결과를 분석합니다.

MCP 서버, 도구, 프롬프트 및 스킬의 테스트 주도 개발을 위한 pytest 플러그인입니다. 먼저 테스트를 작성하세요. AI 분석이 설계를 이끌게 하세요.

왜?

당신의 MCP 서버는 모든 단위 테스트를 통과합니다. 그런데 LLM이 이를 사용하려고 할 때:

  • 잘못된 도구를 선택하고,
  • 쓰레기 같은 매개변수를 전달하거나,
  • 시스템 프롬프트를 무시합니다.

왜냐하면 당신은 코드를 테스트했을 뿐, AI 인터페이스는 테스트하지 않았기 때문입니다.

LLM에게 당신의 API는:

  • 도구 설명,
  • 스키마, 그리고
  • 프롬프트

— 함수와 타입이 아닙니다. 나쁜 도구 설명을 잡아내는 컴파일러도 없고, 혼란스러운 스키마를 표시하는 린터도 없습니다. 전통적인 테스트로는 이를 검증할 수 없습니다.

작동 방식

테스트를 자연어 프롬프트로 작성합니다. Agent는 LLM을 여러분의 도구와 결합하며, 발생한 일에 대해 단언합니다:

from pytest_aitest import Agent, Provider, MCPServer

async def test_balance_query(aitest_run):
    agent = Agent(
        provider=Provider,
        # … configure your tools / server here …
    )
    # … write your natural‑language test here …
  • Documentation – 전체 가이드 및 API 레퍼런스
  • PyPIuv add pytest-aitest
  • Sample Report – AI 분석을 실제로 확인해 보세요
  • GitHubStar pytest‑aitest on GitHub

기여를 환영합니다! 오픈 소스 및 커뮤니티 주도.

0 조회
Back to Blog

관련 글

더 보기 »