Development Guide¶
🤖 AI Summary
Development setup and testing guide. All tests use real match data from two pro matches (8461956309 and 8594217096). CI caches replay files and parsed data for fast test execution (~2 minutes). Tests verify actual service outputs against known match values.
Quick Start¶
# Clone and setup
git clone https://github.com/DeepBlueCoding/mcp-replay-dota2.git
cd mcp-replay-dota2
uv sync
# Run tests (requires replay files)
uv run pytest
# Run CI pipeline
uv run ruff check src/ tests/ dota_match_mcp_server.py
uv run mypy src/ dota_match_mcp_server.py --ignore-missing-imports
uv run pytest
Test Replay Files¶
Tests require two replay files in ~/dota2/replays/:
| Match ID | Description | File |
|---|---|---|
| 8461956309 | Primary test match | 8461956309.dem |
| 8594217096 | Secondary test match (OG game) | 8594217096.dem |
Download replays via the MCP server's download_replay tool or manually from OpenDota/Dotabuff.
Testing Philosophy¶
All tests validate against real match data, not mock/synthetic events. This ensures:
- Catch real bugs - If parsing logic changes, tests fail with actual data
- Verify actual values - Tests assert specific deaths, items, fights from known matches
- No false confidence - Mock tests can pass while production fails
Test Structure¶
tests/
├── conftest.py # Session-scoped fixtures, replay caching
├── test_fight_analyzer.py # Fight detection, highlights (real patterns)
├── test_rotation_service.py # Rotation analysis (real rotations)
├── test_model_validation.py # Service outputs (real values)
├── test_combat_log.py # Combat log parsing
├── test_lane_service.py # Lane analysis
├── test_farming_service.py # Farming patterns
└── ...
Test Data Reference¶
Match 8461956309 (Primary)¶
Verified data points used in tests:
| Category | Data |
|---|---|
| Deaths | 31 total, first blood Earthshaker by Disruptor at 4:48 |
| Runes | 19 pickups, Naga Siren arcane at 6:15 |
| Objectives | 4 Roshan, 14 towers (first: Dire mid T1 at 11:09 by Nevermore) |
| Fights | 24 detected |
| Rotations | 24 total, Shadow Demon most active (6) |
| Lanes | Dire won top, Radiant won mid and bot |
| CS@10 | Juggernaut 63, Nevermore 105 |
| Lane Scores | Radiant 251.5, Dire 237.5 |
Fight Highlights Verified:
- Fissure hitting 2 heroes at 6:06
- Echo Slam hitting 4 heroes at 46:45
- Requiem hitting 4 heroes at 46:45
- Earthshaker double kill at 46:45
- BKB+Blink combo (ES initiator) at 38:27
- Coordinated ES+Nevermore ults at 38:27
- Medusa Outworld Staff save at 38:27
Match 8594217096 (Secondary)¶
| Category | Data |
|---|---|
| Deaths | 53 game deaths, first blood Batrider by Pugna at 1:24 |
| Objectives | 3 Roshan, 14 towers, 5 couriers |
| Rotations | 36 total, Juggernaut most active |
CI Pipeline¶
GitHub Actions Workflow¶
# .github/workflows/test.yml
jobs:
test:
steps:
- Restore replay cache (~/dota2/replays/)
- Restore parsed data cache (~/.cache/mcp_dota2/parsed_replays_v2/)
- Run ruff lint
- Run mypy type check
- Run pytest
- Save caches
Cache Strategy¶
- Replay files: Cached in CI, ~150MB per replay
- Parsed data: Cached to skip re-parsing, instant test startup
- Cache key: Based on test match IDs
Adding New Tests¶
1. Use Real Data¶
def test_something_real(self, combat_service, parsed_replay_data):
"""Test with actual match data."""
deaths = combat_service.get_hero_deaths(parsed_replay_data)
# Assert ACTUAL values from the match
assert len(deaths) == 31
assert deaths[0].victim == "earthshaker"
assert deaths[0].killer == "disruptor"
2. Never Use Synthetic Events¶
# ❌ BAD - Mock data can hide real bugs
def test_with_mock():
fake_events = [CombatLogEvent(...)] # Synthetic
result = process(fake_events)
assert result.success # Passes but doesn't test real parsing
# ✅ GOOD - Real data catches actual issues
def test_with_real(self, parsed_replay_data):
result = service.process(parsed_replay_data)
assert result.deaths == 31 # Actual value from match
3. Use Session-Scoped Fixtures¶
# conftest.py provides parsed data once per test session
@pytest.fixture(scope="session")
def parsed_replay_data():
rs = ReplayService()
return asyncio.run(rs.get_parsed_data(TEST_MATCH_ID))
# Tests reuse the same parsed data
def test_feature_a(self, parsed_replay_data):
...
def test_feature_b(self, parsed_replay_data):
... # Same data, no re-parsing
Running Specific Tests¶
# Run single test file
uv run pytest tests/test_fight_analyzer.py -v
# Run specific test class
uv run pytest tests/test_rotation_service.py::TestMatch8461956309Rotations -v
# Run with coverage
uv run pytest --cov=src --cov-report=html
# Run fast tests only (no replay parsing)
uv run pytest -m fast
Common Issues¶
Missing Replay Files¶
Solution: Download replays to ~/dota2/replays/ or run CI with cached replays.
Slow Tests¶
First run parses replays (~30s each). Subsequent runs use cache (~3s total).
Test Value Mismatch¶
If a test fails with wrong values, the service logic may have changed. Update tests with new verified values from manual inspection.