smithery/imehr

python-testing-guidelines

pytest patterns and best practices

Installation

$ npx skills add smithery/imehr --skill python-testing-guidelines

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/imehr · top by installs.

npx skills add smithery/imehr

Browse all from smithery/imehr

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Version1.0.0

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 9,598 B
  • docs SUMMARY.md 67 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Python Testing Guidelines

Overview

This skill provides patterns for testing Python applications with pytest, including fixtures, mocking, async testing, and test organization.

Quick Reference

Pattern When to Use Example
Fixture Reusable test setup @pytest.fixture
Parametrize Test multiple inputs @pytest.mark.parametrize
Mock Replace dependencies mocker.patch()
Async Test Test async code @pytest.mark.asyncio

Project Configuration

# pyproject.toml
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
addopts = "-v --tb=short"
filterwarnings = ["ignore::DeprecationWarning"]

[tool.coverage.run]
source = ["app"]
omit = ["*/tests/*", "*/__init__.py"]

Core Patterns

Pattern 1: Test Structure (AAA)

# ✅ CORRECT: Arrange-Act-Assert pattern
import pytest
from app.services.user_service import UserService

class TestUserService:
    """Tests for UserService."""

    async def test_create_user_with_valid_data(self, user_service, mock_repo):
        """Should create user when data is valid."""
        # Arrange
        user_data = {"email": "[email protected]", "name": "Test User"}
        mock_repo.get_by_email.return_value = None
        mock_repo.create.return_value = User(id=1, **user_data)

        # Act
        result = await user_service.create(user_data)

        # Assert
        assert result.email == "[email protected]"
        assert result.name == "Test User"
        mock_repo.create.assert_called_once()

    async def test_create_user_raises_on_duplicate_email(self, user_service, mock_repo):
        """Should raise ConflictError when email exists."""
        # Arrange
        mock_repo.get_by_email.return_value = User(id=1, email="[email protected]")

        # Act & Assert
        with pytest.raises(ConflictError, match="Email already registered"):
            await user_service.create({"email": "[email protected]", "name": "Test"})

Pattern 2: Fixtures

# conftest.py - Shared fixtures
import pytest
from unittest.mock import AsyncMock, MagicMock
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from app.models import Base
from app.services.user_service import UserService
from app.repositories.user_repository import UserRepository

# Database fixtures
@pytest.fixture
async def db_engine():
    """Create test database engine."""
    engine = create_async_engine("sqlite+aiosqlite:///:memory:")
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield engine
    await engine.dispose()

@pytest.fixture
async def db_session(db_engine):
    """Create test database session."""
    async with AsyncSession(db_engine) as session:
        yield session

# Repository fixtures
@pytest.fixture
def mock_user_repo():
    """Mock user repository."""
    repo = AsyncMock(spec=UserRepository)
    repo.get_by_id.return_value = None
    repo.get_by_email.return_value = None
    return repo

# Service fixtures
@pytest.fixture
def user_service(mock_user_repo):
    """User service with mocked repository."""
    return UserService(mock_user_repo)

# Test data fixtures
@pytest.fixture
def sample_user():
    """Sample user data."""
    return {
        "email": "[email protected]",
        "name": "Test User",
        "password": "securepassword123"
    }

Pattern 3: Parametrized Tests

# ✅ CORRECT: Parametrized tests for multiple scenarios
import pytest

@pytest.mark.parametrize("email,expected_valid", [
    ("[email protected]", True),
    ("[email protected]", True),
    ("[email protected]", True),
    ("invalid-email", False),
    ("@example.com", False),
    ("user@", False),
    ("", False),
])
def test_email_validation(email: str, expected_valid: bool):
    """Should validate email format correctly."""
    result = is_valid_email(email)
    assert result == expected_valid

@pytest.mark.parametrize("password,expected_errors", [
    ("short", ["Password must be at least 8 characters"]),
    ("nodigits!", ["Password must contain a digit"]),
    ("NOLOWER1", ["Password must contain lowercase"]),
    ("ValidPass1!", []),
])
def test_password_validation(password: str, expected_errors: list[str]):
    """Should validate password requirements."""
    errors = validate_password(password)
    assert errors == expected_errors

Pattern 4: Mocking

# ✅ CORRECT: Mocking external dependencies
import pytest
from unittest.mock import AsyncMock, patch, MagicMock

class TestEmailService:
    async def test_send_welcome_email(self, mocker):
        """Should send welcome email with correct template."""
        # Mock the email client
        mock_client = AsyncMock()
        mocker.patch("app.services.email_service.email_client", mock_client)

        service = EmailService()
        await service.send_welcome("[email protected]", "John")

        mock_client.send.assert_called_once()
        call_args = mock_client.send.call_args
        assert call_args.kwargs["to"] == "[email protected]"
        assert "Welcome" in call_args.kwargs["subject"]

    async def test_handles_email_failure(self, mocker):
        """Should handle email sending failure gracefully."""
        mock_client = AsyncMock()
        mock_client.send.side_effect = EmailError("Connection failed")
        mocker.patch("app.services.email_service.email_client", mock_client)

        service = EmailService()
        # Should not raise, just log
        await service.send_welcome("[email protected]", "John")

# Context manager for patching
class TestExternalAPI:
    async def test_fetch_data(self):
        """Should fetch and transform external data."""
        mock_response = {"data": [{"id": 1, "name": "Item"}]}

        with patch("httpx.AsyncClient.get") as mock_get:
            mock_get.return_value = MagicMock(
                status_code=200,
                json=lambda: mock_response
            )
            result = await fetch_external_data()

        assert len(result) == 1
        assert result[0]["name"] == "Item"

Pattern 5: Async Testing

# ✅ CORRECT: Async test patterns
import pytest
from httpx import AsyncClient
from app.main import app

# Using pytest-asyncio
@pytest.mark.asyncio
async def test_async_function():
    """Test async function directly."""
    result = await async_operation()
    assert result == expected_value

# Integration test with async client
@pytest.fixture
async def async_client():
    """Async HTTP client for testing."""
    async with AsyncClient(app=app, base_url="http://test") as client:
        yield client

class TestAPI:
    async def test_create_user_endpoint(self, async_client, db_session):
        """Should create user via API."""
        response = await async_client.post(
            "/api/users",
            json={"email": "[email protected]", "name": "Test", "password": "pass1234"}
        )

        assert response.status_code == 201
        data = response.json()
        assert data["email"] == "[email protected]"

    async def test_get_user_not_found(self, async_client):
        """Should return 404 for non-existent user."""
        response = await async_client.get("/api/users/999")
        assert response.status_code == 404

Pattern 6: Test Organization

tests/
├── conftest.py              # Shared fixtures
├── unit/                    # Unit tests
│   ├── services/
│   │   ├── test_user_service.py
│   │   └── test_email_service.py
│   └── utils/
│       └── test_validators.py
├── integration/             # Integration tests
│   ├── test_api_users.py
│   └── test_api_posts.py
└── e2e/                     # End-to-end tests
    └── test_user_flow.py

Anti-Patterns

Don't: Test Implementation Details

# ❌ BAD: Testing internal implementation
def test_user_service_calls_repo_twice():
    await service.create(data)
    assert mock_repo.get_by_email.call_count == 1
    assert mock_repo.create.call_count == 1  # Brittle!

# ✅ GOOD: Test behavior/outcome
def test_user_service_creates_user():
    result = await service.create(data)
    assert result.email == data["email"]

Don't: Share Mutable State

# ❌ BAD: Mutable fixture state
@pytest.fixture
def users():
    return []  # Same list shared across tests!

# ✅ GOOD: Factory fixture
@pytest.fixture
def users():
    return []  # Fresh list for each test (if scope="function")

Don't: Ignore Test Isolation

# ❌ BAD: Tests depend on order
class TestUser:
    user_id = None

    def test_create(self):
        TestUser.user_id = create_user().id

    def test_get(self):
        get_user(TestUser.user_id)  # Fails if test_create didn't run!

Useful pytest Markers

@pytest.mark.skip(reason="Not implemented yet")
@pytest.mark.skipif(sys.platform == "win32", reason="Unix only")
@pytest.mark.xfail(reason="Known bug #123")
@pytest.mark.slow  # Custom marker for slow tests
@pytest.mark.integration  # Custom marker

Resources

Topic Link
Fixtures [mdc:resources/fixtures.md]
Mocking [mdc:resources/mocking.md]
Async Testing [mdc:resources/async-testing.md]
Coverage [mdc:resources/coverage.md]