pytest-generator
About
This skill generates pytest-based unit tests for Python code, creating comprehensive test files that follow pytest conventions. It automatically produces tests with proper fixtures, mocking, and parametrization to add missing coverage or test new features. Use it when you need to quickly generate or expand pytest test suites for your Python modules.
Documentation
Pytest Generator Skill
Purpose
This skill generates pytest-based unit tests for Python code, following pytest conventions, best practices, and project standards. It creates comprehensive test suites with proper fixtures, mocking, parametrization, and coverage.
When to Use
- Generate pytest tests for Python modules
- Create test files for new Python features
- Add missing test coverage to existing Python code
- Need pytest-specific patterns (fixtures, markers, parametrize)
Test File Naming Convention
Source to Test Mapping:
- Source:
src/tools/feature/core.py - Test:
tests/test_core.py - Pattern:
test_<source_filename>.py
Examples:
src/utils/validator.py→tests/test_validator.pysrc/models/user.py→tests/test_user.pysrc/services/auth.py→tests/test_auth.py
Pytest Test Generation Workflow
1. Analyze Python Source Code
Read the source file:
# Read the source to understand structure
cat src/tools/feature/core.py
Identify test targets:
- Public functions to test
- Classes and methods
- Error conditions
- Edge cases
- Dependencies (imports, external calls)
Output: List of functions/classes requiring tests
2. Generate Test File Structure
Create test file with proper naming:
"""
Unit tests for [module name].
This module tests:
- [Functionality 1]
- [Functionality 2]
- Error handling and edge cases
"""
import pytest
from unittest.mock import Mock, MagicMock, patch, call
from typing import Any, Dict, List, Optional
from pathlib import Path
# Import functions/classes to test
from src.tools.feature.core import (
function_to_test,
ClassToTest,
CustomException,
)
# ============================================================================
# Fixtures
# ============================================================================
@pytest.fixture
def sample_data() -> Dict[str, Any]:
"""
Sample data for testing.
Returns:
Dictionary with test data
"""
return {
"id": 1,
"name": "test",
"value": 123,
}
@pytest.fixture
def mock_dependency() -> Mock:
"""
Mock external dependency.
Returns:
Configured mock object
"""
mock = Mock()
mock.method.return_value = {"status": "success"}
mock.validate.return_value = True
return mock
@pytest.fixture
def temp_directory(tmp_path: Path) -> Path:
"""
Temporary directory for test files.
Args:
tmp_path: pytest temporary directory fixture
Returns:
Path to test directory
"""
test_dir = tmp_path / "test_data"
test_dir.mkdir()
return test_dir
# ============================================================================
# Test Classes (for testing classes)
# ============================================================================
class TestClassName:
"""Tests for ClassName."""
def test_init_valid_params_creates_instance(self):
"""Test initialization with valid parameters."""
# Arrange & Act
instance = ClassToTest(param="value")
# Assert
assert instance.param == "value"
assert instance.initialized is True
def test_method_valid_input_returns_expected(self, sample_data):
"""Test method with valid input."""
# Arrange
instance = ClassToTest()
# Act
result = instance.method(sample_data)
# Assert
assert result["processed"] is True
assert result["id"] == sample_data["id"]
def test_method_invalid_input_raises_error(self):
"""Test method with invalid input raises error."""
# Arrange
instance = ClassToTest()
invalid_data = None
# Act & Assert
with pytest.raises(ValueError, match="Invalid input"):
instance.method(invalid_data)
# ============================================================================
# Test Functions
# ============================================================================
def test_function_valid_input_returns_expected(sample_data):
"""Test function with valid input returns expected result."""
# Arrange
expected = "processed"
# Act
result = function_to_test(sample_data)
# Assert
assert result == expected
def test_function_empty_input_returns_empty():
"""Test function with empty input returns empty result."""
# Arrange
empty_input = {}
# Act
result = function_to_test(empty_input)
# Assert
assert result == {}
def test_function_none_input_raises_error():
"""Test function with None input raises ValueError."""
# Arrange
invalid_input = None
# Act & Assert
with pytest.raises(ValueError, match="Input cannot be None"):
function_to_test(invalid_input)
def test_function_with_mock_dependency(mock_dependency):
"""Test function with mocked external dependency."""
# Arrange
input_data = {"key": "value"}
# Act
result = function_using_dependency(input_data, mock_dependency)
# Assert
assert result["status"] == "success"
mock_dependency.method.assert_called_once_with(input_data)
@patch('src.tools.feature.core.external_api_call')
def test_function_with_patched_external(mock_api):
"""Test function with patched external API call."""
# Arrange
mock_api.return_value = {"data": "test"}
input_data = {"key": "value"}
# Act
result = function_with_api(input_data)
# Assert
assert result["data"] == "test"
mock_api.assert_called_once()
# ============================================================================
# Parametrized Tests
# ============================================================================
@pytest.mark.parametrize("input_value,expected", [
("[email protected]", True),
("invalid.email", False),
("", False),
(None, False),
("no@domain", False),
("@no-user.com", False),
])
def test_validation_multiple_inputs(input_value, expected):
"""Test validation with multiple input scenarios."""
# Act
result = validate_input(input_value)
# Assert
assert result == expected
@pytest.mark.parametrize("user_type,permission", [
("admin", "all"),
("moderator", "edit"),
("user", "read"),
("guest", "none"),
])
def test_permissions_by_user_type(user_type, permission):
"""Test permissions based on user type."""
# Arrange
user = {"type": user_type}
# Act
result = get_permissions(user)
# Assert
assert result == permission
# ============================================================================
# Async Tests
# ============================================================================
@pytest.mark.asyncio
async def test_async_function_success():
"""Test async function with successful execution."""
# Arrange
input_data = {"key": "value"}
# Act
result = await async_function(input_data)
# Assert
assert result.success is True
assert result.data == input_data
@pytest.mark.asyncio
async def test_async_function_with_mock():
"""Test async function with mocked dependency."""
# Arrange
mock_service = Mock()
mock_service.fetch = AsyncMock(return_value={"data": "test"})
input_data = {"key": "value"}
# Act
result = await async_function_with_service(input_data, mock_service)
# Assert
assert result["data"] == "test"
mock_service.fetch.assert_awaited_once()
# ============================================================================
# Exception Tests
# ============================================================================
def test_custom_exception_raised():
"""Test that custom exception is raised."""
# Arrange
invalid_input = "invalid"
# Act & Assert
with pytest.raises(CustomException):
function_that_raises(invalid_input)
def test_exception_message_content():
"""Test exception message contains expected content."""
# Arrange
invalid_input = "invalid"
# Act & Assert
with pytest.raises(CustomException, match="Expected error message"):
function_that_raises(invalid_input)
def test_exception_attributes():
"""Test exception has expected attributes."""
# Arrange
invalid_input = "invalid"
# Act & Assert
with pytest.raises(CustomException) as exc_info:
function_that_raises(invalid_input)
assert exc_info.value.code == 400
assert "field" in exc_info.value.details
# ============================================================================
# File Operation Tests
# ============================================================================
def test_save_file(temp_directory):
"""Test file saving functionality."""
# Arrange
file_path = temp_directory / "test_file.txt"
content = "test content"
# Act
save_file(file_path, content)
# Assert
assert file_path.exists()
assert file_path.read_text() == content
def test_read_file(temp_directory):
"""Test file reading functionality."""
# Arrange
file_path = temp_directory / "test_file.txt"
expected_content = "test content"
file_path.write_text(expected_content)
# Act
content = read_file(file_path)
# Assert
assert content == expected_content
def test_file_not_found_raises_error(temp_directory):
"""Test reading non-existent file raises error."""
# Arrange
missing_file = temp_directory / "missing.txt"
# Act & Assert
with pytest.raises(FileNotFoundError):
read_file(missing_file)
# ============================================================================
# Marker Examples
# ============================================================================
@pytest.mark.slow
def test_slow_operation():
"""Test slow operation (marked as slow)."""
# This test can be skipped with: pytest -m "not slow"
pass
@pytest.mark.integration
def test_integration_scenario():
"""Test integration scenario (marked as integration)."""
# Run only integration tests: pytest -m integration
pass
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Requires Python 3.9+")
def test_python39_feature():
"""Test feature that requires Python 3.9+."""
pass
# ============================================================================
# Fixture Scope Examples
# ============================================================================
@pytest.fixture(scope="module")
def expensive_setup():
"""
Expensive setup that runs once per module.
Returns:
Setup result
"""
# Setup runs once for entire test module
result = perform_expensive_setup()
yield result
# Teardown runs once after all tests
cleanup(result)
@pytest.fixture(scope="function")
def per_test_setup():
"""
Setup that runs before each test function.
Returns:
Setup result
"""
# Setup runs before each test
result = setup()
yield result
# Teardown runs after each test
teardown(result)
Deliverable: Complete pytest test file
Pytest-Specific Patterns
1. Fixtures
Basic fixture:
@pytest.fixture
def sample_user():
"""Create sample user for testing."""
return User(name="Test User", email="[email protected]")
Fixture with setup and teardown:
@pytest.fixture
def database_connection():
"""Database connection with cleanup."""
# Setup
conn = connect_to_database()
yield conn # Test uses connection here
# Teardown
conn.close()
Fixture with parameters:
@pytest.fixture(params=["sqlite", "postgres", "mysql"])
def database_type(request):
"""Parametrized database fixture."""
return request.param
def test_with_all_databases(database_type):
"""Test runs 3 times, once per database."""
db = connect(database_type)
assert db.connected
Fixture scopes:
@pytest.fixture(scope="function") # Default: runs per test
def per_test():
pass
@pytest.fixture(scope="class") # Runs once per test class
def per_class():
pass
@pytest.fixture(scope="module") # Runs once per module
def per_module():
pass
@pytest.fixture(scope="session") # Runs once per session
def per_session():
pass
2. Parametrize
Basic parametrization:
@pytest.mark.parametrize("input,expected", [
(2, 4),
(3, 9),
(4, 16),
])
def test_square(input, expected):
assert square(input) == expected
Multiple parameters:
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(2, 3, 5),
(10, 20, 30),
])
def test_add(a, b, expected):
assert add(a, b) == expected
Named parameters:
@pytest.mark.parametrize("test_input,expected", [
pytest.param("valid", True, id="valid_input"),
pytest.param("invalid", False, id="invalid_input"),
pytest.param("", False, id="empty_input"),
])
def test_validation(test_input, expected):
assert validate(test_input) == expected
3. Markers
Built-in markers:
@pytest.mark.skip(reason="Not implemented yet")
def test_future_feature():
pass
@pytest.mark.skipif(sys.platform == "win32", reason="Unix only")
def test_unix_feature():
pass
@pytest.mark.xfail(reason="Known bug #123")
def test_buggy_feature():
pass
@pytest.mark.slow
def test_slow_operation():
pass
Custom markers (in pytest.ini):
[pytest]
markers =
slow: marks tests as slow
integration: marks tests as integration tests
unit: marks tests as unit tests
smoke: marks tests as smoke tests
4. Mocking with pytest
Mock with pytest-mock:
def test_with_mocker(mocker):
"""Test using pytest-mock plugin."""
mock_api = mocker.patch('module.api_call')
mock_api.return_value = {"status": "success"}
result = function_using_api()
assert result["status"] == "success"
mock_api.assert_called_once()
Mock attributes:
def test_mock_attributes(mocker):
"""Test with mocked object attributes."""
mock_obj = mocker.Mock()
mock_obj.property = "value"
mock_obj.method.return_value = 42
assert mock_obj.property == "value"
assert mock_obj.method() == 42
5. Async Testing
Async test:
@pytest.mark.asyncio
async def test_async_function():
"""Test async function."""
result = await async_function()
assert result.success
@pytest.mark.asyncio
async def test_async_with_mock(mocker):
"""Test async with mocked async call."""
mock_service = mocker.Mock()
mock_service.fetch = AsyncMock(return_value="data")
result = await function_with_async_call(mock_service)
assert result == "data"
mock_service.fetch.assert_awaited_once()
Test Generation Strategy
For Functions
- Happy path test: Normal successful execution
- Edge case tests: Empty input, max values, min values
- Error tests: Invalid input, None values
- Dependency tests: Mock external dependencies
For Classes
- Initialization tests: Valid params, invalid params
- Method tests: Each public method
- State tests: Verify state changes
- Property tests: Getters and setters
- Error tests: Exception handling
For Modules
- Import tests: Module can be imported
- Public API tests: All public functions/classes
- Integration tests: Module interactions
- Configuration tests: Config loading and validation
Running Pytest
Basic commands:
# Run all tests
pytest
# Run specific file
pytest tests/test_module.py
# Run specific test
pytest tests/test_module.py::test_function
# Run with verbose output
pytest -v
# Run with coverage
pytest --cov=src --cov-report=html --cov-report=term-missing
# Run with markers
pytest -m "not slow"
pytest -m integration
# Run in parallel (with pytest-xdist)
pytest -n auto
# Run with output
pytest -s # Show print statements
pytest -v -s # Verbose + output
Coverage commands:
# Generate coverage report
pytest --cov=src --cov-report=html
# View HTML report
open htmlcov/index.html
# Check coverage threshold
pytest --cov=src --cov-fail-under=80
# Show missing lines
pytest --cov=src --cov-report=term-missing
Best Practices
- Use descriptive test names:
test_function_condition_expected_result - Follow AAA pattern: Arrange, Act, Assert
- One assertion per test (generally)
- Use fixtures for setup: Reusable test setup
- Mock external dependencies: Isolate unit under test
- Parametrize similar tests: Reduce code duplication
- Use markers for organization: Group related tests
- Keep tests independent: No test depends on another
- Test edge cases: Empty, None, max values
- Test error conditions: Exceptions and failures
Quality Checklist
Before marking tests complete:
- Test file properly named (
test_<module>.py) - All public functions/classes tested
- Happy path tests included
- Edge case tests included
- Error condition tests included
- External dependencies mocked
- Fixtures used for reusable setup
- Tests follow AAA pattern
- Test names are descriptive
- All tests pass
- Coverage ≥ 80%
- No flaky tests
- Tests run quickly
Integration with Testing Workflow
Input: Python source file to test Process: Analyze → Generate structure → Write tests → Run & verify Output: pytest test file with ≥ 80% coverage Next Step: Integration testing or code review
Remember
- Follow naming convention:
test_<source_file>.py - Use pytest fixtures for reusable setup
- Parametrize to reduce duplication
- Mock external calls to isolate tests
- Test behavior, not implementation
- Aim for 80%+ coverage
- Keep tests fast and independent
Quick Install
/plugin add https://github.com/matteocervelli/llms/tree/main/pytest-generatorCopy and paste this command in Claude Code to install this skill
GitHub 仓库
Related Skills
evaluating-llms-harness
TestingThis Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.
webapp-testing
TestingThis Claude Skill provides a Playwright-based toolkit for testing local web applications through Python scripts. It enables frontend verification, UI debugging, screenshot capture, and log viewing while managing server lifecycles. Use it for browser automation tasks but run scripts directly rather than reading their source code to avoid context pollution.
finishing-a-development-branch
TestingThis skill helps developers complete finished work by verifying tests pass and then presenting structured integration options. It guides the workflow for merging, creating PRs, or cleaning up branches after implementation is done. Use it when your code is ready and tested to systematically finalize the development process.
go-test
MetaThe go-test skill provides expertise in Go's standard testing package and best practices. It helps developers implement table-driven tests, subtests, benchmarks, and coverage strategies while following Go conventions. Use it when writing test files, creating mocks, detecting race conditions, or organizing integration tests in Go projects.
