name: tdd-guide description: Test-Driven Development workflow guide for Backend.AI (Red-Green-Refactor cycle, scenario definition, pytest fixtures, with_tables, mock repositories, pants test) invoke_method: automatic auto_execute: false enabled: true
TDD Workflow Guide
This skill guides you through Test-Driven Development (TDD) workflow for Backend.AI.
Purpose
When implementing new features or fixing bugs:
- Define test scenarios (success + exception cases)
- Write failing tests first
- Implement minimum code to pass tests
- Refactor with confidence
Parameters
This skill takes no parameters. It guides you through the TDD cycle.
TDD Cycle Overview
1. Define Scenarios → 2. Write Tests → 3. Verify Failure → 4. Implement → 5. Pass → 6. Refactor
↑______________|
Key Principle: Red → Green → Refactor
- Red: Write a failing test
- Green: Make it pass with minimum code
- Refactor: Improve code while keeping tests green
Step 1: Define Test Scenarios
Before writing any code, document:
- Success scenarios (expected behavior)
- Exception scenarios (error cases, edge cases)
Scenario Template
## Test Target: {Feature/Component Name}
### Success Scenarios
1. {Primary success case}
2. {Secondary success case}
3. {Edge case that should succeed}
### Exception Scenarios
1. {Invalid input} → Expected error: {ErrorType}
2. {Constraint violation} → Expected error: {ErrorType}
3. {Resource not found} → Expected error: {ErrorType}
4. {Boundary condition} → Expected behavior: {empty result/default value}
Example: Domain Fair Share Query
## Test Target: DomainFairShare Entity Retrieval
### Success Scenarios
1. Domain with fair share record → Return domain with details populated
2. Domain without fair share record → Return domain with details=None
3. Mixed domains (some with/without records) → Return all with correct details
### Exception Scenarios
1. Non-existent resource_group → Raise ResourceGroupNotFound
2. Resource group with no domains → Raise NoDomainsInResourceGroup
3. Pagination offset > total_count → Return empty list (total_count indicates end)
Step 2: Write Failing Tests
Write tests BEFORE implementation.
Test Structure Pattern
Test Class:
import pytest
class TestFeature:
"""Tests for Feature component."""
@pytest.fixture
async def scenario_data(
self,
database_engine: ExtendedAsyncSAEngine,
) -> AsyncGenerator[DataRow, None]:
"""Fixture describing scenario."""
# Setup with with_tables - see tests/CLAUDE.md
yield data
# Cleanup
async def test_expected_behavior(
self,
repository: FeatureRepository,
scenario_data: DataRow,
) -> None:
"""Test success: Expected behavior description."""
result = await repository.method(scenario_data.id)
assert result.field == expected_value
See tests/CLAUDE.md for:
with_tablesusage patterns- Fixture setup examples
- Database test guidelines
Test Guidelines
Fixtures:
- Name describes scenario:
domain_with_record,mock_provisioner_failure - Return complete data, not factory functions
- Use
AsyncGenerator[ReturnType, None]for async fixtures
Test Functions:
- Name describes expected behavior:
test_returns_domain_with_record - Focus on: Arrange (fixtures) → Act (call) → Assert (verify)
- One test = one scenario
Type Hints:
- All fixtures, parameters, returns annotated
- Test functions return
-> None
Database Tests:
- Use
with_tablesfor repositories/models (seetests/CLAUDE.mdfor detailed usage) - Include all FK dependencies in proper order
- Clean up in reverse order (child → parent)
Step 3: Verify Test Failure
Run tests and confirm they fail for the right reason.
# Run specific test file
pants test tests/manager/repositories/test_feature.py
# Run specific test
pants test tests/manager/repositories/test_feature.py::TestFeature::test_returns_domain_with_record
Expected failure reasons:
ImportError- Component doesn't exist yet (good!)AttributeError- Method not implemented (good!)AssertionError- Logic incorrect (need to verify this is expected)
If tests pass unexpectedly:
- Verify test logic is correct
- Check if feature already implemented
- Ensure test actually exercises the code
Step 4: Implement Minimum Code
Write the simplest code that makes tests pass.
Implementation Principles
Concise and focused
- Each function has clear, single purpose
- Keep implementations brief (< 30 lines per function)
- Extract helpers when logic exceeds ~20 lines
No premature optimization
- Don't add features not covered by tests
- Don't add configurability not required
- Don't add error handling for impossible cases
Complete type annotations
- All parameters and returns typed
- Use domain types (SessionId, not str)
- No
Anytypes
Explicit error handling
- Raise specific exceptions (inherit from BackendAIError)
- Never silent failures (return None/empty)
- Provide clear error messages
See implementation examples:
repositories/fair_share/repository.py- Complete repositoryservices/storage_namespace/service.py- Service implementation
Step 5: Run Tests (Green)
Verify all tests pass.
# Run tests for changed code
pants test --changed-since=HEAD~1 --changed-dependents=transitive
# Run specific test class
pants test tests/manager/repositories/test_feature.py::TestFeature
All tests must pass:
- Success scenarios ✓
- Exception scenarios ✓
- No regressions in other tests ✓
If tests fail:
- Check failure reason (logic error, missing case)
- Fix implementation
- Re-run tests
- Do NOT skip failing tests
Step 6: Refactor
Improve code while keeping tests green.
Refactoring Checklist
- Extract long functions (> 30 lines)
- Remove duplication
- Improve naming
- Add missing type hints
- Simplify complex conditionals
- Apply repository base patterns (if applicable)
Run Quality Checks
After refactoring, always run:
# Format code
pants fmt ::
# Fix auto-fixable issues
pants fix ::
# Check linting
pants lint --changed-since=HEAD~1
# Check types
pants check --changed-since=HEAD~1
# Run tests
pants test --changed-since=HEAD~1 --changed-dependents=transitive
Fix all errors - never suppress:
- Do NOT use
# noqato suppress linter warnings - Do NOT use
# type: ignoreto suppress type errors - Fix root cause instead of suppressing
See BUILDING.md for quality enforcement details.
Refactoring principles:
- Extract long functions into smaller, focused helpers
- Add comprehensive type hints
- Use descriptive names
- Improve readability without changing behavior
Test Strategy by Component
Choose testing approach based on what you're testing:
Repositories & Models
- Real database with
with_tables(detailed usage intests/CLAUDE.md) - Test actual queries, transactions, constraints
- Verify FK relationships work
Services & Handlers
- Unit tests with mocking
- Mock repository calls
- Focus on business logic
See examples: tests/unit/manager/services/
Why This Distinction?
- Repositories: Integration points where actual behavior matters
- Services: Logic verification where isolation improves speed
See tests/CLAUDE.md for detailed testing strategies.
TDD Workflow Examples
See complete examples in:
tests/unit/manager/repositories/- Repository tests withwith_tablestests/unit/manager/services/- Service tests with mockingtests/CLAUDE.md- Testing patterns and strategies
Integration with Repository Development
When implementing repositories, combine with /repository-guide:
1. Read repository-guide:
- Understand base patterns
- Review existing implementations
2. Follow TDD workflow:
- Define scenarios (this skill)
- Write tests with
with_tables - Implement using base utilities
- Refactor
Example workflow:
# 1. Study repository patterns
Read /repository-guide
# 2. Define test scenarios (Step 1)
# Document in .claude/tasks/{feature}.md
# 3. Write failing tests (Step 2)
# Create tests/manager/repositories/test_{feature}.py
# Add BUILD file with python_tests()
# 4. Run tests (Step 3)
pants test tests/manager/repositories/test_{feature}.py
# 5. Implement using base patterns (Step 4)
# Apply Querier, BatchQuerier, Creator, etc.
# 6. Run tests (Step 5)
pants test tests/manager/repositories/test_{feature}.py
# 7. Refactor + quality checks (Step 6)
pants fmt ::
pants check --changed-since=HEAD~1
pants lint --changed-since=HEAD~1
Common TDD Mistakes
Mistake 1: Writing Implementation First
❌ Wrong: Implement → Write tests to verify ✅ Correct: Write tests → Implement to pass
Mistake 2: Testing Implementation Details
❌ Wrong: Assert internal method calls, private state ✅ Correct: Test public behavior, outcomes
Mistake 3: Large Test/Implementation Cycles
❌ Wrong: Write 20 tests → Implement everything ✅ Correct: One test → Small implementation → Repeat
Mistake 4: Skipping Failure Verification
❌ Wrong: Write test → Implement → Run (pass) ✅ Correct: Write test → Run (fail) → Implement → Run (pass)
Mistake 5: Suppressing Quality Errors
❌ Wrong: Add # noqa or # type: ignore
✅ Correct: Fix root cause
Cross-References
tests/CLAUDE.md- Testing guidelines and strategies/repository-guide- Repository implementation patternsBUILDING.md- Quality enforcement and build commandssrc/ai/backend/manager/repositories/README.md- Repository layer overview
Summary
TDD Cycle:
- Define scenarios (success + exceptions)
- Write failing tests (with fixtures)
- Verify failure (red state)
- Implement minimum (concise, typed, explicit errors)
- Pass tests (green state)
- Refactor (improve while keeping green)
Quality Rules:
- Run
pants fmt,pants fixafter refactoring - Run
pants lint,pants checkbefore commit - Fix all errors - never suppress
- Tests must pass - never skip
Test Strategy:
- Repositories: Real DB with
with_tables - Services: Unit tests with mocking
- Always add BUILD file to test directories
Remember: Red → Green → Refactor → Repeat