ワンクリックで
python-tdd-workflow
Enforces test-driven development - writes tests before code, uses fakes over mocks, maintains 80%+ coverage.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Enforces test-driven development - writes tests before code, uses fakes over mocks, maintains 80%+ coverage.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Backend testing patterns — API request construction, response verification, database state checks, error handling testing, and adaptive tool detection.
Frontend testing patterns using Playwright — navigation, interaction, assertions, screenshots on failure, and common UI testing scenarios.
Test report format with QA-XXX issue IDs compatible with code-review plugin. Defines report structure, severity levels, issue format with canonical fields, and detailed results.
Test plan structure, naming conventions, edge case generation rules, and file saving conventions for QA test plans.
Enforces AppVerk Swift coding standards across all code.
Structured concurrency and thread safety patterns in modern Swift.
| name | python-tdd-workflow |
| description | Enforces test-driven development - writes tests before code, uses fakes over mocks, maintains 80%+ coverage. |
| activation | MUST load when writing tests, fixing bugs, or refactoring Python code |
| allowed-tools | Read, Grep, Glob, Bash(pytest:*), Bash(make:*), Bash(uv:*), Bash(python:*), Bash(coverage:*) |
assert False — use raise AssertionError("explanation") insteadmake test or uv run pytest) not just specific testspytest.mark.parametrize for similar test cases instead of duplicating testsThis skill ensures all code development follows TDD principles with comprehensive test coverage.
ALWAYS write tests first, then implement code to make tests pass.
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.
For each user journey, create comprehensive test cases:
# tests/test_semantic_search.py
import pytest
from app.services.search import search_markets
class TestSemanticSearch:
async def test_returns_relevant_markets_for_query(self):
# Test implementation
pass
async def test_handles_empty_query_gracefully(self):
# Test edge case
pass
async def test_falls_back_to_substring_search_when_redis_unavailable(self):
# Test fallback behavior
pass
async def test_sorts_results_by_similarity_score(self):
# Test sorting logic
pass
pytest
# Tests should fail - we haven't implemented yet
Write minimal code to make tests pass:
# app/services/search.py
async def search_markets(query: str) -> list[dict]:
"""Search markets using semantic similarity."""
# Implementation here
pass
pytest
# Tests should now pass
Improve code quality while keeping tests green:
pytest --cov=app --cov-report=term-missing
# Verify 80%+ coverage achieved
# tests/unit/test_market_service.py
import pytest
from app.services.market import MarketService
from tests.fakes import FakeMarketRepository
class TestMarketService:
@pytest.fixture
def repository(self):
return FakeMarketRepository()
@pytest.fixture
def service(self, repository):
return MarketService(repository=repository)
def test_creates_market_with_valid_data(self, service):
market = service.create(
name="Test Market",
description="A test market"
)
assert market.name == "Test Market"
assert market.slug == "test-market"
def test_raises_error_for_duplicate_name(self, service, repository):
repository.add_existing(name="Existing Market")
with pytest.raises(ValueError, match="Market already exists"):
service.create(name="Existing Market", description="...")
def test_validates_market_end_date(self, service):
with pytest.raises(ValueError, match="End date must be in the future"):
service.create(
name="Test",
description="...",
end_date="2020-01-01"
)
# tests/integration/test_markets_api.py
import pytest
from httpx import AsyncClient
from app.main import app
from tests.fixtures import seed_test_markets
class TestMarketsAPI:
@pytest.fixture
async def client(self):
async with AsyncClient(app=app, base_url="http://test") as client:
yield client
@pytest.fixture(autouse=True)
async def setup_test_data(self, test_db):
"""Seed test database with sample markets."""
await seed_test_markets(test_db)
async def test_returns_markets_successfully(self, client):
response = await client.get("/api/markets")
data = response.json()
assert response.status_code == 200
assert data["success"] is True
assert isinstance(data["data"], list)
async def test_validates_query_parameters(self, client):
response = await client.get("/api/markets?limit=invalid")
assert response.status_code == 422 # Validation error
async def test_filters_markets_by_status(self, client):
response = await client.get("/api/markets?status=active")
data = response.json()
assert response.status_code == 200
assert all(m["status"] == "active" for m in data["data"])
# tests/functional/test_market_workflow.py
import pytest
from httpx import AsyncClient
from app.main import app
class TestMarketSearchWorkflow:
"""Black-box functional tests for market search feature."""
@pytest.fixture
async def client(self):
async with AsyncClient(app=app, base_url="http://test") as client:
yield client
@pytest.fixture
async def seeded_markets(self, client):
"""Seed test markets for functional tests."""
markets = [
{"name": "Election 2024", "description": "Presidential election"},
{"name": "Super Bowl Winner", "description": "NFL championship"},
{"name": "Election Day Weather", "description": "Weather on election"},
]
created = []
for market in markets:
response = await client.post("/api/markets", json=market)
created.append(response.json()["data"])
yield created
# Cleanup handled by test database rollback
async def test_user_can_search_and_filter_markets(
self, client, seeded_markets
):
# Search for markets
response = await client.get(
"/api/markets/search",
params={"q": "election"}
)
assert response.status_code == 200
results = response.json()["data"]
# Verify search results returned
assert len(results) == 2 # Two election-related markets
# Verify results contain search term
for result in results:
assert "election" in result["name"].lower() or \
"election" in result["description"].lower()
# Filter by status
response = await client.get(
"/api/markets/search",
params={"q": "election", "status": "active"}
)
assert response.status_code == 200
filtered = response.json()["data"]
assert all(m["status"] == "active" for m in filtered)
class TestMarketCreationWorkflow:
"""Black-box functional tests for market creation."""
@pytest.fixture
async def client(self):
async with AsyncClient(app=app, base_url="http://test") as client:
yield client
@pytest.fixture
async def authenticated_client(self, client):
"""Get authenticated client with creator permissions."""
response = await client.post("/api/auth/login", json={
"email": "creator@test.com",
"password": "testpass123"
})
token = response.json()["token"]
client.headers["Authorization"] = f"Bearer {token}"
yield client
async def test_user_can_create_new_market(self, authenticated_client):
# Create market via API
response = await authenticated_client.post("/api/markets", json={
"name": "Test Market",
"description": "Test description",
"end_date": "2025-12-31"
})
assert response.status_code == 201
data = response.json()
assert data["success"] is True
assert data["data"]["name"] == "Test Market"
assert data["data"]["slug"] == "test-market"
# Verify market is retrievable
market_id = data["data"]["id"]
get_response = await authenticated_client.get(f"/api/markets/{market_id}")
assert get_response.status_code == 200
assert get_response.json()["data"]["name"] == "Test Market"
project/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── services/
│ │ ├── __init__.py
│ │ └── market.py
│ ├── repositories/
│ │ ├── __init__.py
│ │ └── market.py
│ └── api/
│ ├── __init__.py
│ └── routes/
│ └── markets.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py # Shared fixtures
│ ├── fakes/ # Fake implementations
│ │ ├── __init__.py
│ │ ├── repositories.py # FakeMarketRepository, etc.
│ │ └── services.py # FakeSearchService, etc.
│ ├── fixtures/ # Test data helpers
│ │ ├── __init__.py
│ │ └── markets.py # seed_test_markets(), etc.
│ ├── unit/
│ │ ├── __init__.py
│ │ ├── test_market_service.py # Unit tests
│ │ └── test_validators.py
│ ├── integration/
│ │ ├── __init__.py
│ │ └── test_markets_api.py # Integration tests
│ └── functional/
│ ├── __init__.py
│ ├── test_market_workflow.py # Functional tests
│ └── test_trading_workflow.py
└── pyproject.toml
| Scenario | Use |
|---|---|
| Repository/data access | Fake (in-memory implementation) |
| Business logic dependencies | Fake (simplified implementation) |
| 3rd party API calls (OpenAI, Stripe) | Mock |
| Database operations in integration tests | Test database with rollback |
| External HTTP requests | Mock or fake HTTP server |
# tests/fakes/__init__.py
"""Fake implementations for testing."""
from .repositories import FakeMarketRepository, FakeUserRepository
from .services import FakeEmailService, FakeSearchService
# tests/fakes/repositories.py
from typing import Optional
from app.models import Market
class FakeMarketRepository:
"""In-memory fake repository for testing."""
def __init__(self):
self._markets: dict[str, Market] = {}
self._id_counter = 1
def add_existing(self, **kwargs) -> Market:
"""Helper to seed test data."""
market = Market(id=self._id_counter, **kwargs)
self._markets[market.slug] = market
self._id_counter += 1
return market
async def get_by_slug(self, slug: str) -> Optional[Market]:
return self._markets.get(slug)
async def get_all(self) -> list[Market]:
return list(self._markets.values())
async def save(self, market: Market) -> Market:
market.id = self._id_counter
self._markets[market.slug] = market
self._id_counter += 1
return market
async def exists(self, name: str) -> bool:
return any(m.name == name for m in self._markets.values())
# tests/fakes/services.py
class FakeSearchService:
"""Fake semantic search for testing."""
def __init__(self):
self._results: list[dict] = []
def set_results(self, results: list[dict]):
"""Configure search results for test."""
self._results = results
async def search(self, query: str) -> list[dict]:
# Simple substring matching for tests
return [r for r in self._results if query.lower() in r["name"].lower()]
class FakeEmailService:
"""Fake email service that records sent emails."""
def __init__(self):
self.sent_emails: list[dict] = []
async def send(self, to: str, subject: str, body: str):
self.sent_emails.append({"to": to, "subject": subject, "body": body})
# tests/conftest.py
import pytest
@pytest.fixture
def mock_openai(mocker):
"""Mock OpenAI API - external service requiring mock."""
mock = mocker.patch("app.integrations.openai.client.embeddings.create")
mock.return_value.data = [type("Embedding", (), {"embedding": [0.1] * 1536})()]
return mock
@pytest.fixture
def mock_stripe(mocker):
"""Mock Stripe API - external payment service."""
mock = mocker.patch("app.integrations.stripe.client")
mock.PaymentIntent.create.return_value = {"id": "pi_test", "status": "succeeded"}
return mock
# tests/unit/test_market_service.py
class TestMarketService:
@pytest.fixture
def repository(self):
return FakeMarketRepository()
@pytest.fixture
def search_service(self):
fake = FakeSearchService()
fake.set_results([
{"slug": "election-2024", "name": "Election 2024"},
{"slug": "superbowl", "name": "Super Bowl Winner"},
])
return fake
@pytest.fixture
def service(self, repository, search_service):
return MarketService(
repository=repository,
search_service=search_service
)
def test_search_returns_matching_markets(self, service):
results = service.search("election")
assert len(results) == 1
assert results[0]["slug"] == "election-2024"
pytest --cov=app --cov-report=term-missing --cov-report=html
# pyproject.toml
[tool.coverage.run]
source = ["app"]
branch = true
[tool.coverage.report]
fail_under = 80
show_missing = true
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
"raise NotImplementedError",
]
# Don't test internal state
assert service._internal_cache["key"] == "value"
# Test observable behavior
result = service.get("key")
assert result == "expected_value"
# Breaks easily
assert str(error) == "Error: Invalid input at position 5"
# Resilient to message changes
assert isinstance(error, ValueError)
assert "Invalid input" in str(error)
# Tests depend on each other
class TestUser:
user_id = None
def test_creates_user(self):
TestUser.user_id = create_user()
def test_updates_same_user(self):
update_user(TestUser.user_id) # depends on previous test
# Each test sets up its own data
class TestUser:
@pytest.fixture
def user(self):
return create_test_user()
def test_creates_user(self, user):
assert user.id is not None
def test_updates_user(self, user):
updated = update_user(user.id, name="New Name")
assert updated.name == "New Name"
pytest-watch
# or
ptw -- --testmon
# Tests run automatically on file changes
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: pytest
name: pytest
entry: pytest --tb=short
language: system
types: [python]
pass_filenames: false
# GitHub Actions
- name: Run Tests
run: |
pytest --cov=app --cov-report=xml
- name: Upload Coverage
uses: codecov/codecov-action@v4
with:
files: coverage.xml
Remember: Tests are not optional. They are the safety net that enables confident refactoring, rapid development, and production reliability.
These standards extend the HARD-RULES above with organizational and structural guidance (absorbed from python-coding-standards):
pytest.mark.parametrize.tests/unit — fast, isolated tests of pure logictests/integration — tests that cross process/service boundaries (DB, network, etc.)tests/functional — blackbox tests of the API or feature behavior with faked dependenciestests/e2e — end-to-end tests that exercise the system as a wholesrc/ starting after the repository's domain package (drop the top-level package folder). For code in:
src/<domain_package>/<subpath>/module.py
write tests in:tests/<kind>/<subpath>/test_module.py
where <kind> is one of unit | integration | functional | e2e.test_<module>.py for module-level tests; use test_<thing>_<behavior>.py when clearer.tests/conftest.py for global fixturestests/<kind>/conftest.py for category-scoped fixtures