
test-writer
by sage-base
SKILL.md
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
# Write test first - it will fail (no implementation yet) @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
# Now implement just enough to make test 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
# Refactor with confidence - tests verify behavior class CreatePoliticianUseCase: async def execute(self, input_dto): # Add validation if not input_dto.name: raise ValueError("Name required") # Extract to method 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:
- No External Calls: All external services mocked
- Fast Execution: Each test runs in < 1 second
- Isolated: Tests don't depend on each other
- Deterministic: Same result every time
- Clear Names: Test name describes what it tests
- Arrange-Act-Assert: Clear test structure
- Async Properly: Uses
@pytest.mark.asyncioandAsyncMock - Mock Verification: Asserts mock calls when relevant
- Type Hints: Complete type annotations
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():
# ALWAYS use spec= to catch typos and wrong method calls
mock = AsyncMock(spec=ILLMService)
mock.generate_text.return_value = "Mocked response"
return mock
⚠️ Why spec= is CRITICAL:
# ❌ WITHOUT spec= - typos go undetected
mock = AsyncMock()
await mock.genrate_text("prompt") # Typo! Test still passes!
# ✅ WITH spec= - typos caught immediately
mock = AsyncMock(spec=ILLMService)
await mock.genrate_text("prompt") # AttributeError!
Use AsyncMock for async methods, never MagicMock:
# ❌ WRONG - MagicMock for async function
mock_repo = MagicMock(spec=IPoliticianRepository)
result = await mock_repo.create(politician) # Error!
# ✅ CORRECT - AsyncMock for async function
mock_repo = AsyncMock(spec=IPoliticianRepository)
result = await mock_repo.create(politician) # Works!
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):
# Setup mock
mock_repo.save.return_value = Politician(id=1, name="Test")
# Execute
result = usecase.execute(input_dto)
# Assert
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
# Run all tests
docker compose -f docker/docker-compose.yml [-f docker/docker-compose.override.yml] exec sagebase uv run pytest
# Run specific test file
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
# Run with coverage
docker compose -f docker/docker-compose.yml [-f docker/docker-compose.override.yml] exec sagebase uv run pytest --cov=src
# Run only unit tests
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.
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon