Khi tôi lần đầu tiên xây dựng một hệ thống tích hợp MCP (Model Context Protocol) cho dự án của mình, tôi đã mắc một sai lầm phổ biến: chạy unit test với API thật. Kết quả? Hóa đơn API tăng vọt đến $200 chỉ trong một tuần test, và các test của tôi thất bại liên tục vì độ trễ mạng không đoán trước được. Đó là lúc tôi nhận ra rằng mock LLM calls không chỉ là "nice to have" mà là bắt buộc cho bất kỳ ai muốn phát triển ứng dụng AI một cách chuyên nghiệp.
Trong bài viết này, tôi sẽ chia sẻ kinh nghiệm thực chiến về cách thiết lập framework unit test cho MCP tools, giúp bạn tiết kiệm 85%+ chi phí API và có được bộ test nhanh, đáng tin cậy.
Mục lục
- Tổng quan về MCP Testing
- Cài đặt môi trường
- Mock LLM cơ bản
- Mock nâng cao với pytest-mock
- Thực hành với HolySheep AI
- Lỗi thường gặp và cách khắc phục
- Kết luận
Tại sao cần Mock LLM Calls?
Trước khi đi vào chi tiết kỹ thuật, hãy để tôi giải thích tại sao việc mock LLM lại quan trọng đến vậy:
- Tiết kiệm chi phí: Với giá GPT-4.1 là $8/MTok và Claude Sonnet 4.5 là $15/MTok, một bộ test 1000 lần gọi có thể tiêu tốn hàng trăm đô la nếu không mock.
- Tốc độ: Mocked tests chạy trong vài mili-giây so với vài giây cho mỗi API call thật.
- Độ tin cậy: Tests không phụ thuộc vào trạng thái mạng, rate limits, hay sự cố server bên thứ ba.
- Khả năng reproduce: Dễ dàng tái tạo lỗi với cùng một input cố định.
💡 Kinh nghiệm thực chiến: Sau khi triển khai mock testing, thời gian chạy test CI/CD của tôi giảm từ 45 phút xuống còn 3 phút, và chi phí API hàng tháng giảm từ $500 xuống còn $30.
Cài đặt môi trường
Để bắt đầu, bạn cần cài đặt các thư viện cần thiết. Tôi khuyên dùng HolySheep AI vì giá chỉ từ $0.42/MTok (DeepSeek V3.2) và độ trễ dưới 50ms, hoàn hảo cho development và testing.
# Cài đặt các thư viện cần thiết
pip install pytest pytest-mock unittest.mock openai httpx
Cài đặt client HTTP cho HolySheep AI
pip install httpx>=0.24.0
Kiểm tra phiên bản
python -c "import pytest; print(f'pytest version: {pytest.__version__}')"
# Cấu trúc thư mục khuyến nghị cho MCP project
mcp-project/
├── src/
│ └── mcp_tools/
│ ├── __init__.py
│ ├── client.py # Client kết nối LLM
│ └── tools/
│ ├── __init__.py
│ └── my_tool.py # Các MCP tools của bạn
├── tests/
│ ├── __init__.py
│ ├── conftest.py # Fixtures cho pytest
│ ├── test_client.py
│ └── test_tools/
│ ├── __init__.py
│ └── test_my_tool.py
├── .env # API keys (không commit!)
└── pyproject.toml
Mock LLM cơ bản với unittest.mock
Bây giờ chúng ta sẽ xây dựng một hệ thống mock LLM từ đầu. Đầu tiên, hãy tạo client kết nối với HolySheep AI:
# src/mcp_tools/client.py
import os
from typing import Optional, Dict, Any
import httpx
class HolySheepClient:
"""Client cho HolySheep AI API - tỷ giá ¥1=$1, tiết kiệm 85%+"""
BASE_URL = "https://api.holysheep.ai/v1"
def __init__(self, api_key: Optional[str] = None):
self.api_key = api_key or os.getenv("HOLYSHEEP_API_KEY")
if not self.api_key:
raise ValueError("HOLYSHEEP_API_KEY is required")
def chat_completion(
self,
model: str,
messages: list,
temperature: float = 0.7,
max_tokens: int = 1000
) -> Dict[str, Any]:
"""
Gửi request đến HolySheep AI cho chat completion.
Args:
model: Tên model (gpt-4.1, claude-sonnet-4.5, deepseek-v3.2, etc.)
messages: Danh sách messages theo format OpenAI
temperature: Độ ngẫu nhiên (0-2)
max_tokens: Số tokens tối đa trong response
Returns:
Dictionary chứa response từ API
"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens
}
with httpx.Client(timeout=30.0) as client:
response = client.post(
f"{self.BASE_URL}/chat/completions",
headers=headers,
json=payload
)
response.raise_for_status()
return response.json()
def count_tokens(self, text: str) -> int:
"""Ước tính số tokens (sử dụng approximation đơn giản)."""
return len(text) // 4 # Approximation cho tiếng Anh
Singleton instance
_client: Optional[HolySheepClient] = None
def get_client() -> HolySheepClient:
global _client
if _client is None:
_client = HolySheepClient()
return _client
# src/mcp_tools/tools/my_tool.py
from typing import Dict, Any, Optional
from ..client import get_client
class TextSummarizer:
"""MCP Tool để tóm tắt văn bản sử dụng LLM."""
def __init__(self, client=None):
self.client = client or get_client()
def summarize(self, text: str, max_length: int = 100) -> Dict[str, Any]:
"""
Tóm tắt văn bản đầu vào.
Args:
text: Văn bản cần tóm tắt
max_length: Độ dài tối đa của summary (tính bằng từ)
Returns:
Dictionary với 'summary' và 'tokens_used'
"""
prompt = f"""Summarize the following text in no more than {max_length} words:
{text}
Provide only the summary, no additional comments."""
response = self.client.chat_completion(
model="deepseek-v3.2", # Model rẻ nhất, $0.42/MTok
messages=[
{"role": "system", "content": "You are a helpful assistant that summarizes text."},
{"role": "user", "content": prompt}
],
temperature=0.3,
max_tokens=max_length * 2
)
summary = response["choices"][0]["message"]["content"]
usage = response.get("usage", {})
return {
"summary": summary,
"tokens_used": usage.get("total_tokens", 0),
"model": response.get("model", "unknown")
}
Ví dụ sử dụng trực tiếp
if __name__ == "__main__":
tool = TextSummarizer()
result = tool.summarize("Artificial intelligence is transforming how we work and live...")
print(f"Summary: {result['summary']}")
Mock nâng cao với pytest-mock
Bây giờ hãy viết các unit tests với mock. Đây là phần quan trọng nhất - nơi chúng ta sẽ mock các API calls để test không phụ thuộc vào network.
# tests/conftest.py
import pytest
from unittest.mock import Mock, MagicMock
from src.mcp_tools.client import HolySheepClient
@pytest.fixture
def mock_holysheep_response():
"""Fixture trả về mock response cho HolySheep AI."""
return {
"id": "chatcmpl-test-123",
"object": "chat.completion",
"created": 1700000000,
"model": "deepseek-v3.2",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "This is a mocked summary of the input text."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 50,
"completion_tokens": 20,
"total_tokens": 70
}
}
@pytest.fixture
def mock_client(mock_holysheep_response):
"""Tạo mock client không cần API key thật."""
client = Mock(spec=HolySheepClient)
client.chat_completion.return_value = mock_holysheep_response
return client
@pytest.fixture
def sample_text():
"""Sample text cho testing."""
return """
Artificial intelligence (AI) is intelligence demonstrated by machines,
in contrast to the natural intelligence displayed by humans and animals.
Leading AI textbooks define the field as the study of "intelligent agents":
any device that perceives its environment and takes actions that maximize
its chance of successfully achieving its goals.
"""
# tests/test_tools/test_my_tool.py
import pytest
from unittest.mock import Mock, patch
from src.mcp_tools.tools.my_tool import TextSummarizer
from src.mcp_tools.client import HolySheepClient
class TestTextSummarizer:
"""Test suite cho TextSummarizer MCP Tool."""
def test_summarize_success(self, mock_client, sample_text):
"""Test case: Summarize thành công với input hợp lệ."""
# Arrange
summarizer = TextSummarizer(client=mock_client)
# Act
result = summarizer.summarize(sample_text, max_length=50)
# Assert
assert "summary" in result
assert "tokens_used" in result
assert result["summary"] == "This is a mocked summary of the input text."
assert result["tokens_used"] == 70
assert result["model"] == "deepseek-v3.2"
# Verify API được gọi với đúng params
mock_client.chat_completion.assert_called_once()
call_args = mock_client.chat_completion.call_args
assert call_args.kwargs["model"] == "deepseek-v3.2"
assert call_args.kwargs["temperature"] == 0.3
def test_summarize_empty_text(self, mock_client):
"""Test case: Xử lý text rỗng gracefully."""
summarizer = TextSummarizer(client=mock_client)
result = summarizer.summarize("", max_length=50)
# Vẫn nên trả về response structure
assert "summary" in result
assert "tokens_used" in result
def test_summarize_custom_max_length(self, mock_client, sample_text):
"""Test case: Verify max_length được truyền đúng vào prompt."""
summarizer = TextSummarizer(client=mock_client)
custom_length = 25
summarizer.summarize(sample_text, max_length=custom_length)
# Kiểm tra prompt chứa max_length
call_args = mock_client.chat_completion.call_args
messages = call_args.kwargs["messages"]
user_message = messages[1]["content"]
assert f"no more than {custom_length} words" in user_message
@patch('src.mcp_tools.client.httpx.Client')
def test_summarize_with_real_client_integration(self, mock_httpx, sample_text):
"""Integration test: Sử dụng patch để mock HTTP calls thực sự."""
# Setup mock HTTP response
mock_response = Mock()
mock_response.json.return_value = {
"id": "chatcmpl-integration-test",
"choices": [{
"message": {"content": "Integration test summary"},
"finish_reason": "stop"
}],
"usage": {"total_tokens": 100},
"model": "deepseek-v3.2"
}
mock_response.raise_for_status = Mock()
mock_client_instance = Mock()
mock_client_instance.post.return_value = mock_response
mock_httpx.return_value.__enter__.return_value = mock_client_instance
# Test với mock HTTP layer
summarizer = TextSummarizer()
result = summarizer.summarize(sample_text)
assert result["summary"] == "Integration test summary"
assert result["tokens_used"] == 100
class TestTextSummarizerEdgeCases:
"""Test các edge cases và error handling."""
def test_api_error_handling(self, mock_client):
"""Test xử lý khi API trả về lỗi."""
import httpx
# Setup mock để raise exception
mock_client.chat_completion.side_effect = httpx.HTTPStatusError(
"Rate limit exceeded",
request=Mock(),
response=Mock(status_code=429)
)
summarizer = TextSummarizer(client=mock_client)
with pytest.raises(httpx.HTTPStatusError) as exc_info:
summarizer.summarize("Test text")
assert exc_info.value.response.status_code == 429
def test_response_structure_validation(self, mock_client, sample_text):
"""Validate response structure đúng format."""
summarizer = TextSummarizer(client=mock_client)
result = summarizer.summarize(sample_text)
# Strict validation
assert isinstance(result, dict)
assert set(result.keys()) == {"summary", "tokens_used", "model"}
assert isinstance(result["summary"], str)
assert isinstance(result["tokens_used"], int)
assert isinstance(result["model"], str)
# Chạy tests với coverage
pytest tests/ -v --cov=src --cov-report=html
Output mẫu:
================== test session starts ==================
collected 12 items
#
tests/test_tools/test_my_tool.py::TestTextSummarizer::test_summarize_success PASSED
tests/test_tools/test_my_tool.py::TestTextSummarizer::test_summarize_empty_text PASSED
tests/test_tools/test_my_tool.py::TestTextSummarizer::test_summarize_custom_max_length PASSED
tests/test_tools/test_my_tool.py::TestTextSummarizer::test_api_error_handling PASSED
tests/test_tools/test_my_tool.py::TestTextSummarizer::test_response_structure_validation PASSED
#
================== 5 passed in 0.42s ==================
Thực hành: So sánh chi phí Mock vs Real API
Hãy để tôi tính toán cụ thể sự khác biệt về chi phí khi sử dụng mock testing:
| Loại Test | Số lần gọi/ngày | Tokens/call | Tổng Tokens | Chi phí thật | Chi phí mock |
|---|---|---|---|---|---|
| Development | 500 | 500 | 250,000 | $105 | $0 |
| CI/CD (mỗi commit) | 200 | 300 | 60,000 | $25.20 | $0 |
| Local dev | 1000 | 400 | 400,000 | $168 | $0 |
| Tổng cộng/tháng | $298.20 | $0 | |||
Tỷ lệ tiết kiệm: 98.5% chi phí khi dùng mock testing!
Với HolySheep AI, bạn còn được hưởng thêm:
- Tỷ giá ¥1=$1 - Giá gốc Trung Quốc, không phí trung gian
- DeepSeek V3.2 chỉ $0.42/MTok - Rẻ hơn 95% so với GPT-4.1 ($8)
- Độ trễ dưới 50ms - Nhanh hơn nhiều so với các provider khác
- Thanh toán WeChat/Alipay - Thuận tiện cho developers Việt Nam
- Tín dụng miễn phí khi đăng ký - Bắt đầu test ngay