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ạ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:

💡 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 TestSố lần gọi/ngàyTokens/callTổng TokensChi phí thậtChi phí mock
Development500500250,000$105$0
CI/CD (mỗi commit)20030060,000$25.20$0
Local dev1000400400,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: