| name | test-writer |
| description | Guides test creation for Polibase following strict testing standards. Activates when writing tests or creating test files. Enforces external service mocking (no real API calls), async/await patterns, test independence, and proper use of pytest-asyncio to prevent CI failures and API costs. |
Test Writer
Purpose
Guide test creation following Polibase testing standards with proper mocking, async/await patterns, and independence from external services.
When to Activate
This skill activates automatically when:
- Writing new tests
- Creating test files in
tests/ directory
- User mentions "test", "pytest", or "testing"
- Reviewing existing test code
⚡ TDD Workflow (Test-First Development)
ALWAYS write tests BEFORE implementation!
Red-Green-Refactor Cycle
-
🔴 Red: Write a failing test
@pytest.mark.asyncio
async def test_create_politician_saves_to_repository():
mock_repo = AsyncMock(spec=IPoliticianRepository)
mock_repo.create.return_value = Politician(id=1, name="山田太郎")
usecase = CreatePoliticianUseCase(mock_repo)
result = await usecase.execute(CreatePoliticianInputDTO(name="山田太郎"))
mock_repo.create.assert_awaited_once()
-
🟢 Green: Write minimal code to pass
class CreatePoliticianUseCase:
async def execute(self, input_dto):
politician = Politician(name=input_dto.name)
await self.repository.create(politician)
-
♻️ Refactor: Improve code while keeping tests green
class CreatePoliticianUseCase:
async def execute(self, input_dto):
if not input_dto.name:
raise ValueError("Name required")
politician = self._create_entity(input_dto)
return await self.repository.create(politician)
TDD Benefits
- ✅ Forces you to think about API design before implementation
- ✅ Tests serve as documentation
- ✅ Refactoring is safe (tests catch regressions)
- ✅ Code is naturally testable (designed for testing)
Remember: If you write implementation first, you're not doing TDD!
🚫 CRITICAL: Never Call External Services
ABSOLUTELY FORBIDDEN in tests:
- ❌ Real API calls to Google Gemini or any LLM
- ❌ Actual HTTP requests to external websites
- ❌ Real database connections (except integration tests)
- ❌ File system operations outside temp directories
- ❌ Network connections of any kind
Why?
- Tests must run in CI/CD without API keys
- Tests must be fast (< 1 second per test)
- Tests must be deterministic (same result every time)
- Tests must not incur API costs
Quick Checklist
Before committing tests:
Test Structure
tests/
├── unit/ # Fast, isolated tests
│ ├── domain/ # Domain entities and services
│ ├── application/ # Use cases (with mocks)
│ └── infrastructure/ # External services (with mocks)
├── integration/ # Tests with real database
├── evaluation/ # LLM evaluation (manual only, not in CI)
└── conftest.py # Shared fixtures
Core Testing Patterns
1. Mocking External Services
Always use AsyncMock with spec= parameter:
from unittest.mock import AsyncMock
@pytest.fixture
def mock_llm_service():
mock = AsyncMock(spec=ILLMService)
mock.generate_text.return_value = "Mocked response"
return mock
⚠️ Why spec= is CRITICAL:
mock = AsyncMock()
await mock.genrate_text("prompt")
mock = AsyncMock(spec=ILLMService)
await mock.genrate_text("prompt")
Use AsyncMock for async methods, never MagicMock:
mock_repo = MagicMock(spec=IPoliticianRepository)
result = await mock_repo.create(politician)
mock_repo = AsyncMock(spec=IPoliticianRepository)
result = await mock_repo.create(politician)
2. Async Tests
Use pytest-asyncio:
@pytest.mark.asyncio
async def test_async_function(mock_repo):
result = await usecase.execute(input_dto)
assert result.success
3. Test Independence
Each test is self-contained:
def test_create_politician(mock_repo):
mock_repo.save.return_value = Politician(id=1, name="Test")
result = usecase.execute(input_dto)
assert result.success
Templates
Use templates in templates/ directory for:
- Domain service tests
- Use case tests with mocks
- Repository integration tests
- External service tests with mocks
Detailed Reference
For comprehensive testing patterns, mocking strategies, and best practices, see reference.md.
Examples
See examples.md for concrete test examples at each layer.
Running Tests
docker compose -f docker/docker-compose.yml [-f docker/docker-compose.override.yml] exec sagebase uv run pytest
docker compose -f docker/docker-compose.yml [-f docker/docker-compose.override.yml] exec sagebase uv run pytest tests/unit/domain/test_speaker_domain_service.py
docker compose -f docker/docker-compose.yml [-f docker/docker-compose.override.yml] exec sagebase uv run pytest --cov=src
docker compose -f docker/docker-compose.yml [-f docker/docker-compose.override.yml] exec sagebase uv run pytest tests/unit/
Common Anti-Patterns
- ❌ Real API Calls: Most common mistake!
- ❌ Testing Implementation Details: Test public interfaces
- ❌ Test Dependencies: Each test must be independent
- ❌ Missing Async/Await: Forget
@pytest.mark.asyncio
- ❌ No Mock Verification: Don't check if mocks were called
See reference.md for detailed explanations and fixes.