@physics91/openrouter-mcp
Version:
A Model Context Protocol (MCP) server for OpenRouter API with Collective Intelligence - Multi-model consensus, ensemble reasoning, and collaborative problem solving
457 lines (352 loc) • 10.5 kB
Markdown
# Testing Guide
Comprehensive testing documentation for the OpenRouter MCP Server, built with Test-Driven Development (TDD) principles.
## Table of Contents
- [Testing Philosophy](#testing-philosophy)
- [Test Structure](#test-structure)
- [Running Tests](#running-tests)
- [Writing Tests](#writing-tests)
- [Test Coverage](#test-coverage)
- [Continuous Integration](#continuous-integration)
- [Troubleshooting Tests](#troubleshooting-tests)
## Testing Philosophy
This project follows strict TDD principles:
1. **Red**: Write a failing test first
2. **Green**: Write minimal code to pass the test
3. **Refactor**: Improve code while keeping tests green
### Test Pyramid
```
/\
/E2E\ (5%) - End-to-end scenarios
/------\
/Integration\ (20%) - Component interactions
/------------\
/ Unit Tests \ (75%) - Individual functions/methods
/----------------\
```
## Test Structure
```
tests/
├── __init__.py
├── conftest.py # Shared fixtures and configuration
├── test_client/ # Client layer tests
│ ├── __init__.py
│ └── test_openrouter.py # OpenRouter client tests
├── test_handlers/ # Handler tests
│ ├── __init__.py
│ ├── test_chat_handler.py
│ └── test_multimodal.py
├── test_mcp_benchmark.py # Benchmarking tests
├── test_metadata.py # Metadata system tests
├── test_models_cache.py # Cache system tests
└── fixtures/ # Test data
├── images/ # Test images
└── responses/ # Mock API responses
```
## Running Tests
### Basic Test Execution
```bash
# Run all tests
pytest
# Run with verbose output
pytest -v
# Run specific test file
pytest tests/test_client/test_openrouter.py
# Run specific test function
pytest tests/test_handlers/test_chat_handler.py::test_chat_completion
# Run tests matching pattern
pytest -k "benchmark"
```
### Test Coverage
```bash
# Run tests with coverage report
pytest --cov=src/openrouter_mcp --cov-report=term-missing
# Generate HTML coverage report
pytest --cov=src/openrouter_mcp --cov-report=html
# View coverage report
open htmlcov/index.html # macOS
start htmlcov/index.html # Windows
```
### Continuous Testing
```bash
# Watch mode - re-run tests on file changes
pytest-watch
# Run tests in parallel
pytest -n auto
# Run only failed tests from last run
pytest --lf
# Run failed tests first, then others
pytest --ff
```
## Writing Tests
### Test File Naming
- Test files must start with `test_`
- Test functions must start with `test_`
- Test classes must start with `Test`
### Basic Test Structure
```python
import pytest
from src.openrouter_mcp.client import OpenRouterClient
class TestOpenRouterClient:
"""Test suite for OpenRouter client."""
def client(self):
"""Create a test client instance."""
return OpenRouterClient(api_key="test-key")
def test_client_initialization(self, client):
"""Test client initializes with correct configuration."""
assert client.api_key == "test-key"
assert client.base_url == "https://openrouter.ai/api/v1"
async def test_chat_completion(self, client, mock_response):
"""Test chat completion request."""
# Arrange
messages = [{"role": "user", "content": "Hello"}]
# Act
response = await client.chat_completion(
model="openai/gpt-4",
messages=messages
)
# Assert
assert response["choices"][0]["message"]["content"]
assert response["model"] == "openai/gpt-4"
```
### Testing Async Code
```python
import pytest
import asyncio
async def test_async_function():
"""Test asynchronous function."""
result = await some_async_function()
assert result == expected_value
# Using pytest-asyncio fixtures
async def async_client():
"""Async fixture for client."""
client = AsyncClient()
await client.connect()
yield client
await client.disconnect()
```
### Mocking and Fixtures
```python
# conftest.py
import pytest
from unittest.mock import Mock, AsyncMock
def mock_httpx_client():
"""Mock httpx client for API calls."""
mock = AsyncMock()
mock.post.return_value.json.return_value = {
"choices": [{"message": {"content": "Test response"}}]
}
return mock
def sample_image_base64():
"""Provide sample base64 encoded image."""
return "data:image/png;base64,iVBORw0KGgoAAAANS..."
# Using fixtures in tests
def test_with_mock(mock_httpx_client):
"""Test using mocked HTTP client."""
# Your test code here
```
### Parametrized Tests
```python
def test_provider_detection(model, expected_provider):
"""Test provider detection for different models."""
provider = detect_provider(model)
assert provider == expected_provider
```
### Testing Error Cases
```python
def test_invalid_api_key():
"""Test handling of invalid API key."""
with pytest.raises(AuthenticationError) as exc_info:
client = OpenRouterClient(api_key="invalid")
client.validate_key()
assert "Invalid API key" in str(exc_info.value)
def test_rate_limit_handling():
"""Test rate limit error handling."""
client = OpenRouterClient()
with pytest.raises(RateLimitError) as exc_info:
# Simulate rate limit scenario
for _ in range(100):
client.make_request()
assert exc_info.value.retry_after > 0
```
### Benchmark Testing
```python
async def test_benchmark_performance():
"""Test benchmarking system performance."""
models = ["openai/gpt-4", "anthropic/claude-3"]
prompt = "Test prompt"
results = await benchmark_models(
models=models,
prompt=prompt,
runs=3
)
assert len(results["results"]) == len(models)
assert all(r["metrics"]["total_time"] > 0 for r in results["results"])
assert results["rankings"]["by_speed"]
```
### Integration Tests
```python
class TestMCPIntegration:
"""Integration tests for MCP server."""
async def mcp_server(self):
"""Start MCP server for testing."""
server = await start_test_server()
yield server
await server.shutdown()
async def test_full_chat_flow(self, mcp_server):
"""Test complete chat flow through MCP."""
# Connect to server
client = MCPClient(server_url=mcp_server.url)
# Send chat request
response = await client.call_tool(
"chat_with_model",
model="openai/gpt-4",
messages=[{"role": "user", "content": "Hello"}]
)
# Verify response
assert response["choices"]
assert response["usage"]["total_tokens"] > 0
```
## Test Coverage
### Coverage Goals
- **Overall**: Minimum 80% coverage
- **Critical paths**: 95% coverage
- **New code**: 100% coverage required
### Coverage Configuration
```ini
# pytest.ini
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
asyncio_mode = auto
[coverage:run]
source = src/openrouter_mcp
omit =
*/tests/*
*/conftest.py
*/__init__.py
[coverage:report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
if __name__ == .__main__.:
```
### Checking Coverage
```bash
# Generate coverage report
coverage run -m pytest
coverage report
# Check specific module coverage
coverage report --include="src/openrouter_mcp/handlers/*"
# Find untested lines
coverage report --show-missing
```
## Continuous Integration
### GitHub Actions Workflow
```yaml
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.9, 3.10, 3.11]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install -r requirements-dev.txt
- name: Run tests
run: |
pytest --cov=src/openrouter_mcp --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v2
```
## Troubleshooting Tests
### Common Issues
#### 1. Async Test Failures
```python
# Problem: Test not marked as async
def test_async_function(): # Wrong
await some_async_function()
# Solution: Mark test as async
async def test_async_function(): # Correct
await some_async_function()
```
#### 2. Fixture Scope Issues
```python
# Problem: Session fixture used in function test
def expensive_resource():
return create_resource()
# Solution: Use appropriate scope
def test_resource():
return create_resource()
```
#### 3. Mock Not Working
```python
# Problem: Mocking wrong path
# Wrong path
# Solution: Mock where it's used
```
### Debug Tips
```bash
# Run tests with detailed output
pytest -vvs
# Show print statements
pytest -s
# Debug with pdb
pytest --pdb
# Show local variables on failure
pytest -l
# Stop on first failure
pytest -x
# Run last failed test with pdb
pytest --lf --pdb
```
## Best Practices
### 1. Test Independence
Each test should be independent and not rely on other tests.
### 2. Clear Test Names
Test names should clearly describe what they test.
### 3. Arrange-Act-Assert
Follow the AAA pattern for test structure.
### 4. Use Fixtures
Leverage fixtures for setup and teardown.
### 5. Test One Thing
Each test should verify one specific behavior.
### 6. Fast Tests
Keep unit tests fast (< 100ms per test).
### 7. Deterministic Tests
Tests should produce the same result every time.
### 8. Meaningful Assertions
Use specific assertions with clear failure messages.
---
**Last Updated**: 2025-01-12
**Version**: 1.0.0