python-testing
This Python testing skill provides comprehensive patterns for pytest-based test development, including test structure conventions, fixture creation with various scopes, fixture dependency management, and parametrized testing for multiple input scenarios. Use it when building reliable test suites for Python applications that require organized setup and teardown procedures, reusable test components, or validation across diverse test cases.
git clone --depth 1 https://github.com/affaan-m/ECC /tmp/python-testing && cp -r /tmp/python-testing/.kiro/skills/python-testing ~/.claude/skills/python-testingSKILL.md
# Python Testing
> This skill provides comprehensive Python testing patterns using pytest as the primary testing framework.
## Testing Framework
Use **pytest** as the testing framework for its powerful features and clean syntax.
### Basic Test Structure
```python
def test_user_creation():
"""Test that a user can be created with valid data"""
user = User(name="Alice", email="alice@example.com")
assert user.name == "Alice"
assert user.email == "alice@example.com"
assert user.is_active is True
```
### Test Discovery
pytest automatically discovers tests following these conventions:
- Files: `test_*.py` or `*_test.py`
- Functions: `test_*`
- Classes: `Test*` (without `__init__`)
- Methods: `test_*`
## Fixtures
Fixtures provide reusable test setup and teardown:
```python
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
@pytest.fixture
def db_session():
"""Provide a database session for tests"""
engine = create_engine("sqlite:///:memory:")
Session = sessionmaker(bind=engine)
session = Session()
# Setup
Base.metadata.create_all(engine)
yield session
# Teardown
session.close()
def test_user_repository(db_session):
"""Test using the db_session fixture"""
repo = UserRepository(db_session)
user = repo.create(name="Alice", email="alice@example.com")
assert user.id is not None
```
### Fixture Scopes
```python
@pytest.fixture(scope="function") # Default: per test
def user():
return User(name="Alice")
@pytest.fixture(scope="class") # Per test class
def database():
db = Database()
db.connect()
yield db
db.disconnect()
@pytest.fixture(scope="module") # Per module
def app():
return create_app()
@pytest.fixture(scope="session") # Once per test session
def config():
return load_config()
```
### Fixture Dependencies
```python
@pytest.fixture
def database():
db = Database()
db.connect()
yield db
db.disconnect()
@pytest.fixture
def user_repository(database):
"""Fixture that depends on database fixture"""
return UserRepository(database)
def test_create_user(user_repository):
user = user_repository.create(name="Alice")
assert user.id is not None
```
## Parametrization
Test multiple inputs with `@pytest.mark.parametrize`:
```python
import pytest
@pytest.mark.parametrize("email,expected", [
("user@example.com", True),
("invalid-email", False),
("", False),
("user@", False),
("@example.com", False),
])
def test_email_validation(email, expected):
result = validate_email(email)
assert result == expected
```
### Multiple Parameters
```python
@pytest.mark.parametrize("name,age,valid", [
("Alice", 25, True),
("Bob", 17, False),
("", 25, False),
("Charlie", -1, False),
])
def test_user_validation(name, age, valid):
result = validate_user(name, age)
assert result == valid
```
### Parametrize with IDs
```python
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
], ids=["lowercase", "another_lowercase"])
def test_uppercase(input, expected):
assert input.upper() == expected
```
## Test Markers
Use markers for test categorization and selective execution:
```python
import pytest
@pytest.mark.unit
def test_calculate_total():
"""Fast unit test"""
assert calculate_total([1, 2, 3]) == 6
@pytest.mark.integration
def test_database_connection():
"""Slower integration test"""
db = Database()
assert db.connect() is True
@pytest.mark.slow
def test_large_dataset():
"""Very slow test"""
process_million_records()
@pytest.mark.skip(reason="Not implemented yet")
def test_future_feature():
pass
@pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+")
def test_new_syntax():
pass
```
**Run specific markers:**
```bash
pytest -m unit # Run only unit tests
pytest -m "not slow" # Skip slow tests
pytest -m "unit or integration" # Run unit OR integration
```
## Mocking
### Using unittest.mock
```python
from unittest.mock import Mock, patch, MagicMock
def test_user_service_with_mock():
"""Test with mock repository"""
mock_repo = Mock()
mock_repo.find_by_id.return_value = User(id="1", name="Alice")
service = UserService(mock_repo)
user = service.get_user("1")
assert user.name == "Alice"
mock_repo.find_by_id.assert_called_once_with("1")
@patch('myapp.services.EmailService')
def test_send_notification(mock_email_service):
"""Test with patched dependency"""
service = NotificationService()
service.send("user@example.com", "Hello")
mock_email_service.send.assert_called_once()
```
### pytest-mock Plugin
```python
def test_with_mocker(mocker):
"""Using pytest-mock plugin"""
mock_repo = mocker.Mock()
mock_repo.find_by_id.return_value = User(id="1", name="Alice")
service = UserService(mock_repo)
user = service.get_user("1")
assert user.name == "Alice"
```
## Coverage Analysis
### Basic Coverage
```bash
pytest --cov=src --cov-report=term-missing
```
### HTML Coverage Report
```bash
pytest --cov=src --cov-report=html
open htmlcov/index.html
```
### Coverage Configuration
```ini
# pytest.ini or pyproject.toml
[tool.pytest.ini_options]
addopts = """
--cov=src
--cov-report=term-missing
--cov-report=html
--cov-fail-under=80
"""
```
### Branch Coverage
```bash
pytest --cov=src --cov-branch
```
## Async Testing
### Testing Async Functions
```python
import pytest
@pytest.mark.asyncio
async def test_async_fetch_user():
"""Test async function"""
user = await fetch_user("1")
assert user.name == "Alice"
@pytest.fixture
async def async_client():
"""Async fixture"""
client = AsyncClient()
await client.connect()
yield client
await client.disconnect()
@pytest.mark.asyncio
async def test_with_async_fixture(async_client):
result = await async_cStructured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports.
Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.
>
Write articles, guides, blog posts, tutorials, newsletter issues, and other long-form content in a distinctive voice derived from supplied examples or brand guidance. Use when the user wants polished written content longer than a paragraph, especially when voice consistency, structure, and credibility matter.
>
Build a source-derived writing style profile from real posts, essays, launch notes, docs, or site copy, then reuse that profile across content, outreach, and social workflows. Use when the user wants voice consistency without generic AI writing tropes.
Bun as runtime, package manager, bundler, and test runner. When to choose Bun vs Node, migration notes, and Vercel support.
>