Agent Skill
2/7/2026tdd-workflow
Use this skill when writing new features, fixing bugs, or refactoring code. Enforces test-driven development with 80%+ coverage including unit, integration, and E2E tests.
A
anton
0GitHub Stars
1Views
npx skills add anton-dovnar/cursor
SKILL.md
| Name | tdd-workflow |
| Description | Use this skill when writing new features, fixing bugs, or refactoring code. Enforces test-driven development with 80%+ coverage including unit, integration, and E2E tests. |
name: tdd-workflow description: Use this skill when writing new features, fixing bugs, or refactoring code. Enforces test-driven development with 80%+ coverage including unit, integration, and E2E tests.
Test-Driven Development Workflow
This skill ensures all code development follows TDD principles with comprehensive test coverage.
When to Activate
- Writing new features or functionality
- Fixing bugs or issues
- Refactoring existing code
- Adding API endpoints
- Creating new components
Core Principles
1. Tests BEFORE Code
ALWAYS write tests first, then implement code to make tests pass.
2. Coverage Requirements
- Minimum 80% coverage (unit + integration + E2E)
- All edge cases covered
- Error scenarios tested
- Boundary conditions verified
3. Test Types
Unit Tests
- Individual functions and utilities
- Component logic
- Pure functions
- Helpers and utilities
Integration Tests
- API endpoints
- Database operations
- Service interactions
- External API calls
E2E Tests (Playwright)
- Critical user flows
- Complete workflows
- Browser automation
- UI interactions
TDD Workflow Steps
Step 1: Write User Journeys
As a [role], I want to [action], so that [benefit]
Example:
As a user, I want to search for markets semantically,
so that I can find relevant markets even without exact keywords.
Step 2: Generate Test Cases
For each user journey, create comprehensive test cases:
import pytest
class TestSemanticSearch:
@pytest.mark.asyncio
async def test_returns_relevant_markets_for_query(self):
# Test implementation
pass
@pytest.mark.asyncio
async def test_handles_empty_query_gracefully(self):
# Test edge case
pass
@pytest.mark.asyncio
async def test_falls_back_to_substring_search_when_redis_unavailable(self):
# Test fallback behavior
pass
@pytest.mark.asyncio
async def test_sorts_results_by_similarity_score(self):
# Test sorting logic
pass
Step 3: Run Tests (They Should Fail)
pytest tests/
# Tests should fail - we haven't implemented yet
Step 4: Implement Code
Write minimal code to make tests pass:
# Implementation guided by tests
async def search_markets(query: str) -> list[dict]:
# Implementation here
pass
Step 5: Run Tests Again
pytest tests/
# Tests should now pass
Step 6: Refactor
Improve code quality while keeping tests green:
- Remove duplication
- Improve naming
- Optimize performance
- Enhance readability
Step 7: Verify Coverage
pytest tests/ --cov=. --cov-report=html
# Verify 80%+ coverage achieved
Testing Patterns
Unit Test Pattern (pytest)
import pytest
from unittest.mock import Mock, patch
from app.services.button_service import ButtonService
class TestButtonService:
def test_renders_with_correct_text(self):
button = ButtonService(text='Click me')
assert button.text == 'Click me'
def test_calls_on_click_when_clicked(self):
handle_click = Mock()
button = ButtonService(on_click=handle_click, text='Click')
button.click()
handle_click.assert_called_once()
def test_is_disabled_when_disabled_prop_is_true(self):
button = ButtonService(disabled=True, text='Click')
assert button.disabled is True
API Integration Test Pattern
import pytest
from httpx import AsyncClient
from fastapi import FastAPI
app = FastAPI()
@pytest.fixture
async def client():
async with AsyncClient(app=app, base_url='http://test') as ac:
yield ac
class TestGetMarkets:
@pytest.mark.asyncio
async def test_returns_markets_successfully(self, client: AsyncClient):
response = await client.get('/api/markets')
data = response.json()
assert response.status_code == 200
assert data['success'] is True
assert isinstance(data['data'], list)
@pytest.mark.asyncio
async def test_validates_query_parameters(self, client: AsyncClient):
response = await client.get('/api/markets?limit=invalid')
assert response.status_code == 400
@pytest.mark.asyncio
async def test_handles_database_errors_gracefully(self, client: AsyncClient):
# Mock database failure
with patch('app.database.get_markets', side_effect=Exception('DB Error')):
response = await client.get('/api/markets')
# Test error handling
assert response.status_code == 500
E2E Test Pattern (Playwright)
from playwright.async_api import Page, expect
import pytest
@pytest.mark.asyncio
async def test_user_can_search_and_filter_markets(page: Page):
# Navigate to markets page
await page.goto('/')
await page.click('a[href="/markets"]')
# Verify page loaded
await expect(page.locator('h1')).to_contain_text('Markets')
# Search for markets
await page.fill('input[placeholder="Search markets"]', 'election')
# Wait for debounce and results
await page.wait_for_timeout(600)
# Verify search results displayed
results = page.locator('[data-testid="market-card"]')
await expect(results).to_have_count(5, timeout=5000)
# Verify results contain search term
first_result = results.first
await expect(first_result).to_contain_text('election', ignore_case=True)
# Filter by status
await page.click('button:has-text("Active")')
# Verify filtered results
await expect(results).to_have_count(3)
@pytest.mark.asyncio
async def test_user_can_create_new_market(page: Page):
# Login first
await page.goto('/creator-dashboard')
# Fill market creation form
await page.fill('input[name="name"]', 'Test Market')
await page.fill('textarea[name="description"]', 'Test description')
await page.fill('input[name="endDate"]', '2025-12-31')
# Submit form
await page.click('button[type="submit"]')
# Verify success message
await expect(page.locator('text=Market created successfully')).to_be_visible()
# Verify redirect to market page
await expect(page).to_have_url(r'/markets/test-market/')
Test File Organization
project/
├── app/
│ ├── services/
│ │ ├── button_service.py
│ │ └── test_button_service.py # Unit tests
│ └── api/
│ └── markets/
│ ├── route.py
│ └── test_route.py # Integration tests
└── tests/
├── e2e/
│ ├── test_markets.py # E2E tests
│ ├── test_trading.py
│ └── test_auth.py
└── conftest.py # Shared fixtures
Mocking External Services
Database Mock
from unittest.mock import Mock, patch
import pytest
@pytest.fixture
def mock_supabase():
mock_client = Mock()
mock_client.from.return_value.select.return_value.eq.return_value = {
'data': [{'id': 1, 'name': 'Test Market'}],
'error': None
}
return mock_client
def test_with_mock_supabase(mock_supabase):
with patch('app.database.supabase', mock_supabase):
# Test implementation
pass
Redis Mock
from unittest.mock import Mock, patch
@pytest.fixture
def mock_redis():
mock_redis_client = Mock()
mock_redis_client.search_markets_by_vector.return_value = [
{'slug': 'test-market', 'similarity_score': 0.95}
]
mock_redis_client.check_redis_health.return_value = {'connected': True}
return mock_redis_client
def test_with_mock_redis(mock_redis):
with patch('app.services.redis_client', mock_redis):
# Test implementation
pass
OpenAI Mock
from unittest.mock import Mock, patch
@pytest.fixture
def mock_openai():
mock_client = Mock()
mock_client.generate_embedding.return_value = [0.1] * 1536 # Mock 1536-dim embedding
return mock_client
def test_with_mock_openai(mock_openai):
with patch('app.services.openai_client', mock_openai):
# Test implementation
pass
Test Coverage Verification
Run Coverage Report
pytest tests/ --cov=. --cov-report=html
Coverage Thresholds
# pytest.ini or setup.cfg
[tool:pytest]
addopts = --cov=. --cov-report=html --cov-report=term-missing
[coverage:run]
source = app
[coverage:report]
fail_under = 80
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
Common Testing Mistakes to Avoid
❌ WRONG: Testing Implementation Details
# Don't test internal state
assert component._count == 5
✅ CORRECT: Test User-Visible Behavior
# Test what users see/experience
result = service.get_count_display()
assert 'Count: 5' in result
❌ WRONG: Brittle Selectors
# Breaks easily
await page.click('.css-class-xyz')
✅ CORRECT: Semantic Selectors
# Resilient to changes
await page.click('button:has-text("Submit")')
await page.click('[data-testid="submit-button"]')
❌ WRONG: No Test Isolation
# Tests depend on each other
def test_creates_user():
# Creates user in database
pass
def test_updates_same_user():
# Depends on previous test's user
pass
✅ CORRECT: Independent Tests
# Each test sets up its own data
@pytest.fixture
def test_user():
return create_test_user()
def test_creates_user(test_user):
# Test logic with isolated user
pass
def test_updates_user(test_user):
# Update logic with isolated user
pass
Continuous Testing
Watch Mode During Development
pytest-watch
# Tests run automatically on file changes
Pre-Commit Hook
# Runs before every commit
pytest tests/ && ruff check .
CI/CD Integration
# GitHub Actions
- name: Run Tests
run: pytest tests/ --cov=. --cov-report=xml
- name: Upload Coverage
uses: codecov/codecov-action@v3
Best Practices
- Write Tests First - Always TDD
- One Assert Per Test - Focus on single behavior
- Descriptive Test Names - Explain what's tested
- Arrange-Act-Assert - Clear test structure
- Mock External Dependencies - Isolate unit tests
- Test Edge Cases - Null, undefined, empty, large
- Test Error Paths - Not just happy paths
- Keep Tests Fast - Unit tests < 50ms each
- Clean Up After Tests - No side effects
- Review Coverage Reports - Identify gaps
Success Metrics
- 80%+ code coverage achieved
- All tests passing (green)
- No skipped or disabled tests
- Fast test execution (< 30s for unit tests)
- E2E tests cover critical user flows
- Tests catch bugs before production
Remember: Tests are not optional. They are the safety net that enables confident refactoring, rapid development, and production reliability.
Skills Info
Original Name:tdd-workflowAuthor:anton
Download