pramoddutta/qaskills/python-testing-patterns

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Comprehensive Python testing best practices with pytest, covering unit testing, integration testing, mocking, fixtures, property-based testing, and test architecture.

Install

npx vskill@latest install pramoddutta/qaskills/python-testing-patterns

SKILL.md

Python Testing Patterns Skill

You are an expert Python developer specializing in testing patterns and best practices. When the user asks you to write, review, or improve Python tests, follow these detailed instructions.

Core Principles

  1. Test behavior, not implementation -- Focus on what the code does, not how it does it.
  2. DAMP over DRY -- Descriptive And Meaningful Phrases over Don't Repeat Yourself in tests.
  3. Arrange-Act-Assert -- Clear structure in every test.
  4. Fast and isolated -- Tests should run in milliseconds and have zero dependencies on each other.
  5. Fail fast with clear messages -- Assertion messages should explain what went wrong.

Project Structure

project/
src/
myapp/
__init__.py
services/
user_service.py
order_service.py
models/
user.py
order.py
utils/
validators.py
formatters.py
tests/
__init__.py
conftest.py
unit/
test_user_service.py
test_validators.py
integration/
test_api_endpoints.py
test_database.py
fixtures/
user_fixtures.py
order_fixtures.py
pytest.ini
pyproject.toml

Configuration

pytest.ini
ini
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
-v
--tb=short
--strict-markers
--cov=src
--cov-report=term-missing
--cov-report=html
--cov-fail-under=80
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
integration: marks integration tests
unit: marks unit tests
smoke: marks smoke tests
skip_ci: marks tests to skip in CI
pyproject.toml
toml
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = [
"-v",
"--strict-markers",
"--cov=src",
"--cov-report=term-missing",
]
[tool.coverage.run]
source = ["src"]
omit = ["tests/*", "*/__init__.py"]
[tool.coverage.report]
fail_under = 80
show_missing = true
skip_covered = false

Unit Testing Patterns

1. Testing Pure Functions
python
# validators.py
from typing import Optional
def is_valid_email(email: str) -> bool:
"""Validate email address format."""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
def calculate_discount(price: float, discount_percent: float) -> float:
"""Calculate discounted price."""
if not 0 <= discount_percent <= 100:
raise ValueError("Discount must be between 0 and 100")
return price * (1 - discount_percent / 100)
python
# test_validators.py
import pytest
from myapp.utils.validators import is_valid_email, calculate_discount
class TestEmailValidation:
"""Test suite for email validation."""
@pytest.mark.parametrize("email", [
"user@example.com",
"first.last@domain.co.uk",
"user+tag@example.com",
"user123@test-domain.com",
])
def test_valid_emails(self, email):
"""Should accept valid email addresses."""
assert is_valid_email(email) is True
@pytest.mark.parametrize("email", [
"",
"not-an-email",
"@missing-local.com",
"missing-at.com",
"spaces here@bad.com",
"missing@domain",
])
def test_invalid_emails(self, email):
"""Should reject invalid email addresses."""
assert is_valid_email(email) is False
class TestDiscountCalculation:
"""Test suite for discount calculation."""
def test_no_discount(self):
"""Should return original price when discount is 0."""
assert calculate_discount(100.0, 0) == 100.0
def test_full_discount(self):
"""Should return 0 when discount is 100%."""
assert calculate_discount(100.0, 100) == 0.0
def test_partial_discount(self):
"""Should calculate correct discounted price."""
assert calculate_discount(100.0, 20) == 80.0
assert calculate_discount(50.0, 10) == 45.0
def test_invalid_discount_raises_error(self):
"""Should raise ValueError for invalid discount percentage."""
with pytest.raises(ValueError, match="Discount must be between 0 and 100"):
calculate_discount(100.0, 101)
with pytest.raises(ValueError, match="Discount must be between 0 and 100"):
calculate_discount(100.0, -10)
2. Testing Classes and Services
python
# user_service.py
from typing import Optional
from myapp.models.user import User
from myapp.repositories.user_repository import UserRepository
from myapp.services.email_service import EmailService
class UserService:
"""Service for managing users."""
def __init__(
self,
user_repository: UserRepository,
email_service: EmailService,
):
self.user_repository = user_repository
self.email_service = email_service
async def create_user(self, email: str, name: str) -> User:
"""Create a new user and send welcome email."""
existing = await self.user_repository.find_by_email(email)
if existing:
raise ValueError("User with this email already exists")
user = await self.user_repository.create(email=email, name=name)
await self.email_service.send_welcome_email(user.email, user.name)
return user
async def get_user(self, user_id: str) -> Optional[User]:
"""Get user by ID."""
return await self.user_repository.find_by_id(user_id)
async def delete_user(self, user_id: str) -> None:
"""Delete user by ID."""
user = await self.user_repository.find_by_id(user_id)
if not user:
raise ValueError("User not found")
await self.user_repository.delete(user_id)
python
# test_user_service.py
import pytest
from unittest.mock import AsyncMock, MagicMock
from myapp.services.user_service import UserService
from myapp.models.user import User
@pytest.fixture
def mock_user_repository():
"""Create a mock UserRepository."""
return AsyncMock()
@pytest.fixture
def mock_email_service():
"""Create a mock EmailService."""
return AsyncMock()
@pytest.fixture
def user_service(mock_user_repository, mock_email_service):
"""Create UserService with mocked dependencies."""
return UserService(
user_repository=mock_user_repository,
email_service=mock_email_service,
)
class TestUserService:
"""Test suite for UserService."""
@pytest.mark.asyncio
async def test_create_user_success(
self,
user_service,
mock_user_repository,
mock_email_service,
):
"""Should create user and send welcome email."""
# Arrange
mock_user_repository.find_by_email.return_value = None
new_user = User(id="123", email="new@example.com", name="New User")
mock_user_repository.create.return_value = new_user
# Act
result = await user_service.create_user("new@example.com", "New User")
# Assert
assert result == new_user
mock_user_repository.find_by_email.assert_called_once_with("new@example.com")
mock_user_repository.create.assert_called_once_with(
email="new@example.com",
name="New User",
)
mock_email_service.send_welcome_email.assert_called_once_with(
"new@example.com",
"New User",
)
@pytest.mark.asyncio
async def test_create_user_duplicate_email_raises_error(
self,
user_service,
mock_user_repository,
mock_email_service,
):
"""Should raise error when user with email already exists."""
# Arrange
existing_user = User(id="456", email="existing@example.com", name="Existing")
mock_user_repository.find_by_email.return_value = existing_user
# Act & Assert
with pytest.raises(ValueError, match="User with this email already exists"):
await user_service.create_user("existing@example.com", "Duplicate")
mock_user_repository.create.assert_not_called()
mock_email_service.send_welcome_email.assert_not_called()
@pytest.mark.asyncio
async def test_get_user_found(self, user_service, mock_user_repository):
"""Should return user when found."""
# Arrange
user = User(id="123", email="user@example.com", name="User")
mock_user_repository.find_by_id.return_value = user
# Act
result = await user_service.get_user("123")
# Assert
assert result == user
mock_user_repository.find_by_id.assert_called_once_with("123")
@pytest.mark.asyncio
async def test_get_user_not_found(self, user_service, mock_user_repository):
"""Should return None when user not found."""
# Arrange
mock_user_repository.find_by_id.return_value = None
# Act
result = await user_service.get_user("nonexistent")
# Assert
assert result is None
@pytest.mark.asyncio
async def test_delete_user_success(self, user_service, mock_user_repository):
"""Should delete existing user."""
# Arrange
user = User(id="123", email="user@example.com", name="User")
mock_user_repository.find_by_id.return_value = user
# Act
await user_service.delete_user("123")
# Assert
mock_user_repository.delete.assert_called_once_with("123")
@pytest.mark.asyncio
async def test_delete_user_not_found_raises_error(
self,
user_service,
mock_user_repository,
):
"""Should raise error when deleting non-existent user."""
# Arrange
mock_user_repository.find_by_id.return_value = None
# Act & Assert
with pytest.raises(ValueError, match="User not found"):
await user_service.delete_user("nonexistent")
mock_user_repository.delete.assert_not_called()

Advanced Fixture Patterns

1. Fixture Factories
python
# conftest.py
import pytest
from typing import Callable
from myapp.models.user import User
@pytest.fixture
def make_user() -> Callable:
"""Factory fixture for creating test users."""
created_users = []
def _make_user(
email: str = None,
name: str = "Test User",
role: str = "user",
) -> User:
if email is None:
email = f"test{len(created_users)}@example.com"
user = User(id=str(len(created_users) + 1), email=email, name=name, role=role)
created_users.append(user)
return user
yield _make_user
# Cleanup happens here if needed
created_users.clear()
# Usage
def test_user_permissions(make_user):
"""Test different user roles."""
admin = make_user(role="admin")
viewer = make_user(role="viewer")
assert admin.can_delete_users() is True
assert viewer.can_delete_users() is False
2. Fixture Dependency Chain
python
@pytest.fixture(scope="session")
def database_url():
"""Provide test database URL."""
return "postgresql://test:test@localhost:5432/test_db"
@pytest.fixture(scope="session")
def database_engine(database_url):
"""Create database engine."""
from sqlalchemy import create_engine
engine = create_engine(database_url)
yield engine
engine.dispose()
@pytest.fixture(scope="function")
def database_session(database_engine):
"""Create a database session for each test."""
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=database_engine)
session = Session()
yield session
session.rollback()
session.close()
@pytest.fixture
def user_repository(database_session):
"""Create UserRepository with test database session."""
from myapp.repositories.user_repository import UserRepository
return UserRepository(database_session)
3. Autouse Fixtures

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
76
Repository forks
1
7-day trend score
22

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 18 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/pramoddutta/qaskills/python-testing-patterns/badge)](https://verified-skill.com/skills/pramoddutta/qaskills/python-testing-patterns)

Something wrong with this skill or its page? Tell us.