- Use app.run_stdio_async() instead of deprecated stdio_server import - Aligns with FastMCP 2.11.3 API - Server now starts correctly with uv run mcp-office-tools - Maintains all MCPMixin functionality and tool registration
277 lines
7.8 KiB
Markdown
277 lines
7.8 KiB
Markdown
# MCP Office Tools Testing Strategy
|
|
|
|
This document outlines the comprehensive testing strategy for the mixin-based FastMCP Office Tools server.
|
|
|
|
## Testing Architecture Overview
|
|
|
|
The testing suite is designed around the mixin architecture pattern and follows FastMCP best practices:
|
|
|
|
### Test Organization
|
|
|
|
```
|
|
tests/
|
|
├── conftest.py # Shared fixtures and configuration
|
|
├── test_server.py # Integration tests for the composed server
|
|
├── test_mixins.py # Mixin architecture and composition tests
|
|
├── test_universal_mixin.py # Unit tests for UniversalMixin
|
|
├── test_word_mixin.py # Unit tests for WordMixin
|
|
├── test_excel_mixin.py # Unit tests for ExcelMixin (future)
|
|
├── test_powerpoint_mixin.py # Unit tests for PowerPointMixin (future)
|
|
└── README.md # This file
|
|
```
|
|
|
|
## Testing Patterns
|
|
|
|
### 1. Mixin Unit Testing
|
|
|
|
Each mixin is tested independently with comprehensive mocking:
|
|
|
|
```python
|
|
@pytest.mark.asyncio
|
|
@patch('mcp_office_tools.utils.validation.resolve_office_file_path')
|
|
@patch('mcp_office_tools.utils.validation.validate_office_file')
|
|
@patch('mcp_office_tools.utils.file_detection.detect_format')
|
|
async def test_extract_text_success(mock_detect, mock_validate, mock_resolve, mixin):
|
|
# Setup mocks
|
|
mock_resolve.return_value = "/test.csv"
|
|
mock_validate.return_value = {"is_valid": True, "errors": []}
|
|
mock_detect.return_value = {"category": "data", "extension": ".csv"}
|
|
|
|
# Mock internal methods
|
|
with patch.object(mixin, '_extract_text_by_category') as mock_extract:
|
|
mock_extract.return_value = {"text": "test", "method_used": "pandas"}
|
|
|
|
result = await mixin.extract_text("/test.csv")
|
|
assert "text" in result
|
|
```
|
|
|
|
### 2. Tool Registration Testing
|
|
|
|
Verify that mixins register tools correctly:
|
|
|
|
```python
|
|
def test_tool_registration_count(self):
|
|
"""Test that all expected tools are registered."""
|
|
app = FastMCP("Test Office Tools")
|
|
|
|
universal = UniversalMixin(app)
|
|
assert len(app._tools) == 6 # 6 universal tools
|
|
|
|
word = WordMixin(app)
|
|
assert len(app._tools) == 7 # 6 universal + 1 word tool
|
|
```
|
|
|
|
### 3. FastMCP Session Testing
|
|
|
|
Test tools through FastMCP's testing framework:
|
|
|
|
```python
|
|
@pytest.mark.asyncio
|
|
async def test_tool_execution_via_session(self):
|
|
"""Test tool execution through FastMCP test session."""
|
|
session = create_test_session(app)
|
|
|
|
result = await session.call_tool("get_supported_formats", {})
|
|
assert "supported_extensions" in result
|
|
```
|
|
|
|
### 4. Error Handling Testing
|
|
|
|
Comprehensive error handling with proper exception types:
|
|
|
|
```python
|
|
@pytest.mark.asyncio
|
|
async def test_extract_text_nonexistent_file(self, mixin):
|
|
"""Test extract_text with nonexistent file raises OfficeFileError."""
|
|
with pytest.raises(OfficeFileError):
|
|
await mixin.extract_text("/nonexistent/file.docx")
|
|
```
|
|
|
|
## Mocking Strategies
|
|
|
|
### File Operations
|
|
|
|
Use the `MockValidationContext` for consistent file operation mocking:
|
|
|
|
```python
|
|
def test_with_mock_validation(mock_validation_context):
|
|
with mock_validation_context(
|
|
resolve_path="/test.docx",
|
|
validation_result={"is_valid": True, "errors": []},
|
|
format_detection={"category": "word", "extension": ".docx"}
|
|
):
|
|
# Test with mocked file operations
|
|
pass
|
|
```
|
|
|
|
### Office Document Processing
|
|
|
|
Mock internal processing methods to test tool logic without file dependencies:
|
|
|
|
```python
|
|
with patch.object(mixin, '_extract_text_by_category') as mock_extract:
|
|
mock_extract.return_value = {
|
|
"text": "extracted text",
|
|
"method_used": "python-docx",
|
|
"methods_tried": ["python-docx"]
|
|
}
|
|
|
|
result = await mixin.extract_text(file_path)
|
|
```
|
|
|
|
## Test Categories
|
|
|
|
### Unit Tests (`@pytest.mark.unit`)
|
|
- Individual mixin functionality
|
|
- Helper method testing
|
|
- Parameter validation
|
|
- Error handling
|
|
|
|
### Integration Tests (`@pytest.mark.integration`)
|
|
- Full server composition
|
|
- Cross-mixin interactions
|
|
- Tool execution via sessions
|
|
- End-to-end workflows
|
|
|
|
### Tool Functionality Tests (`@pytest.mark.tool_functionality`)
|
|
- Specific tool behavior
|
|
- Parameter handling
|
|
- Output validation
|
|
- Method selection logic
|
|
|
|
## Running Tests
|
|
|
|
### All Tests
|
|
```bash
|
|
uv run pytest
|
|
```
|
|
|
|
### Specific Test Categories
|
|
```bash
|
|
# Unit tests only
|
|
uv run pytest -m unit
|
|
|
|
# Integration tests only
|
|
uv run pytest -m integration
|
|
|
|
# Tool functionality tests
|
|
uv run pytest -m tool_functionality
|
|
|
|
# Specific mixin tests
|
|
uv run pytest tests/test_universal_mixin.py
|
|
|
|
# With coverage
|
|
uv run pytest --cov=mcp_office_tools
|
|
```
|
|
|
|
### Fast Development Cycle
|
|
```bash
|
|
# Skip integration tests for faster feedback
|
|
uv run pytest -m "not integration"
|
|
```
|
|
|
|
## Test Fixtures
|
|
|
|
### Shared Fixtures (conftest.py)
|
|
|
|
- `fast_mcp_app`: Clean FastMCP app instance
|
|
- `universal_mixin`: UniversalMixin instance
|
|
- `word_mixin`: WordMixin instance
|
|
- `composed_app`: Fully composed app with all mixins
|
|
- `test_session`: FastMCP test session
|
|
- `temp_dir`: Temporary directory for test files
|
|
- `mock_csv_file`: Temporary CSV file with test data
|
|
- `mock_docx_file`: Mock DOCX file structure
|
|
|
|
### Mock Data Fixtures
|
|
|
|
- `mock_file_validation`: Standard validation response
|
|
- `mock_format_detection`: Standard format detection response
|
|
- `mock_text_extraction_result`: Standard text extraction result
|
|
- `mock_document_metadata`: Standard document metadata
|
|
|
|
## Best Practices
|
|
|
|
### 1. Fast Test Execution
|
|
- Mock all file I/O operations
|
|
- Use temporary files only when necessary
|
|
- Keep tests under 1 second unless marked as integration
|
|
|
|
### 2. Comprehensive Mocking
|
|
- Mock external dependencies at the boundary
|
|
- Test internal logic without external dependencies
|
|
- Use realistic mock data that reflects actual tool behavior
|
|
|
|
### 3. Clear Test Intent
|
|
- One behavior per test
|
|
- Descriptive test names
|
|
- Clear arrange/act/assert structure
|
|
|
|
### 4. Error Testing
|
|
- Test all error conditions
|
|
- Verify specific exception types
|
|
- Test error messages for helpfulness
|
|
|
|
### 5. Tool Functionality Focus
|
|
- Test tool behavior, not just registration
|
|
- Verify output structure and content
|
|
- Test parameter combinations and edge cases
|
|
|
|
## Advanced Testing Patterns
|
|
|
|
### Testing Async Tool Methods Directly
|
|
|
|
```python
|
|
@pytest.mark.asyncio
|
|
async def test_tool_method_directly(universal_mixin):
|
|
"""Test tool method directly without session overhead."""
|
|
# Direct method testing for unit-level validation
|
|
with patch('mcp_office_tools.utils.validation.validate_office_file'):
|
|
result = await universal_mixin.extract_text("/test.csv")
|
|
assert result is not None
|
|
```
|
|
|
|
### Testing Tool Parameter Validation
|
|
|
|
```python
|
|
@pytest.mark.asyncio
|
|
async def test_parameter_validation(mixin):
|
|
"""Test tool parameter validation and handling."""
|
|
# Test various parameter combinations
|
|
result = await mixin.extract_text(
|
|
file_path="/test.csv",
|
|
preserve_formatting=True,
|
|
include_metadata=False,
|
|
method="primary"
|
|
)
|
|
|
|
# Verify parameters were used correctly
|
|
assert result["metadata"]["extraction_method"] != "auto"
|
|
```
|
|
|
|
### Testing Mixin Composition
|
|
|
|
```python
|
|
def test_mixin_composition(self):
|
|
"""Test that mixin composition works correctly."""
|
|
app = FastMCP("Test")
|
|
|
|
# Initialize mixins in order
|
|
universal = UniversalMixin(app)
|
|
word = WordMixin(app)
|
|
|
|
# Verify no tool conflicts
|
|
tool_names = set(app._tools.keys())
|
|
assert len(tool_names) == 7 # 6 + 1, no duplicates
|
|
```
|
|
|
|
## Future Enhancements
|
|
|
|
- [ ] Property-based testing for document processing
|
|
- [ ] Performance benchmarking tests
|
|
- [ ] Memory usage validation
|
|
- [ ] Stress testing with large documents
|
|
- [ ] Network operation testing for URL processing
|
|
- [ ] Security testing for malicious document handling
|
|
|
|
This testing strategy ensures comprehensive coverage of the mixin-based architecture while maintaining fast test execution and clear test organization. |