Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
pytest is the industry-standard Python testing framework, offering powerful features like fixtures, parametrization, markers, plugins, and seamless integration with FastAPI, Django, and Flask. It provides a simple, scalable approach to testing from unit tests to complex integration scenarios.
Key Features:
Fixture system for dependency injection
Parametrization for data-driven tests
Rich assertion introspection (no need for self.assertEqual)
# Discover and run all tests
pytest
# Verbose output
pytest -v
# Show print statements
pytest -s
# Run specific test file
pytest test_math.py
# Run specific test function
pytest test_math.py::test_add
2. Test Classes for Organization
# test_calculator.pyclassCalculator:
defadd(self, a, b):
return a + b
defmultiply(self, a, b):
return a * b
classTestCalculator:
deftest_add(self):
calc = Calculator()
assert calc.add(2, 3) == 5deftest_multiply(self):
calc = Calculator()
assert calc.multiply(4, 5) == 20deftest_add_negative(self):
calc = Calculator()
assert calc.add(-1, -1) == -2
3. Assertions and Expected Failures
import pytest
# Test exception raisingdefdivide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
deftest_divide_by_zero():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(10, 0)
deftest_divide_success():
assert divide(10, 2) == 5.0# Test approximate equalitydeftest_float_comparison():
assert0.1 + 0.2 == pytest.approx(0.3)
# Test containmentdeftest_list_contains():
result = [1, 2, 3, 4]
assert3in result
assertlen(result) == 4
import pytest
# Function scope (default) - runs for each test@pytest.fixture(scope="function")defuser():
return {"id": 1, "name": "Alice"}
# Class scope - runs once per test class@pytest.fixture(scope="class")defdatabase():
db = setup_database()
yield db
db.close()
# Module scope - runs once per test module@pytest.fixture(scope="module")defapi_client():
client = APIClient()
yield client
client.shutdown()
# Session scope - runs once for entire test session@pytest.fixture(scope="session")defapp_config():
return load_config()
Fixture Setup and Teardown
import pytest
import tempfile
import shutil
@pytest.fixturedeftemp_directory():
"""Create a temporary directory for test."""
temp_dir = tempfile.mkdtemp()
print(f"
Setup: Created {temp_dir}")
yield temp_dir # Provide directory to test# Teardown: cleanup after test
shutil.rmtree(temp_dir)
print(f"
Teardown: Removed {temp_dir}")
deftest_file_creation(temp_directory):
file_path = f"{temp_directory}/test.txt"withopen(file_path, "w") as f:
f.write("test content")
assert os.path.exists(file_path)
# pytest.ini
[pytest]
markers =
slow: marks tests as slow (deselect with'-m "not slow"')
integration: marks tests as integration tests
unit: marks tests as unit tests
smoke: marks tests as smoke tests
# test_custom_markers.pyimport pytest
@pytest.mark.unitdeftest_fast_unit():
assertTrue@pytest.mark.integration@pytest.mark.slowdeftest_slow_integration():
# Integration test with databasepass@pytest.mark.smokedeftest_critical_path():
# Smoke test for critical functionalitypass
Run tests by marker:
# Run only unit tests
pytest -m unit
# Run all except slow tests
pytest -m "not slow"# Run integration tests
pytest -m integration
# Run unit AND integration
pytest -m "unit or integration"# Run smoke tests only
pytest -m smoke
classUserService:
defget_user(self, user_id):
# Database callreturn database.fetch_user(user_id)
defget_user_name(self, user_id):
user = self.get_user(user_id)
return user["name"]
deftest_get_user_name(mocker):
service = UserService()
# Mock the get_user method
mocker.patch.object(
service,
"get_user",
return_value={"id": 1, "name": "Alice"}
)
result = service.get_user_name(1)
assert result == "Alice"
Mocking with Side Effects
deftest_retry_on_failure(mocker):
# First call fails, second succeeds
mock_api = mocker.patch("requests.get")
mock_api.side_effect = [
requests.exceptions.Timeout(), # First call
mocker.Mock(json=lambda: {"status": "ok"}) # Second call
]
result = api_call_with_retry()
assert result["status"] == "ok"assert mock_api.call_count == 2
Spy on Calls
deftest_function_called_correctly(mocker):
spy = mocker.spy(module, "function_name")
# Call code that uses the function
module.run_workflow()
# Verify it was calledassert spy.call_count == 1
spy.assert_called_once_with(arg1="value", arg2=42)
Coverage and Reporting
pytest-cov Configuration
# Install
pip install pytest-cov
# Run with coverage
pytest --cov=app --cov-report=html --cov-report=term
# Generate coverage report
pytest --cov=app --cov-report=term-missing
# Coverage with minimum threshold
pytest --cov=app --cov-fail-under=80
deftest_user_service_creates_user():
# Arrange: Setup test data and dependencies
service = UserService(database=mock_db)
user_data = {"email": "test@example.com", "name": "Test"}
# Act: Perform the action being tested
result = service.create_user(user_data)
# Assert: Verify the outcomeassert result.email == "test@example.com"assert result.idisnotNone
# ✅ GOOD: Mock external APIdeftest_fetch_user_data(mocker):
mocker.patch("requests.get", return_value=mock_response)
result = fetch_user_data(user_id=1)
assert result["name"] == "Alice"# ❌ BAD: Real API call in testdeftest_fetch_user_data():
result = fetch_user_data(user_id=1) # Real HTTP request!assert result["name"] == "Alice"
Common Pitfalls
❌ Anti-Pattern 1: Test Depends on Execution Order
# WRONG: Tests should be independentclassTestUserWorkflow:
user_id = Nonedeftest_create_user(self):
user = create_user()
TestUserWorkflow.user_id = user.iddeftest_update_user(self):
# Fails if test_create_user didn't run first!
update_user(TestUserWorkflow.user_id, name="New")
# WRONG: Database not cleaned updeftest_user_creation():
db = setup_database()
user = create_user(db)
assert user.idisnotNone# Database connection not closed!
# Run all tests
pytest
# Verbose output
pytest -v
# Show print statements
pytest -s
# Run specific file
pytest tests/test_api.py
# Run specific test
pytest tests/test_api.py::test_create_user
# Run by marker
pytest -m unit
pytest -m "not slow"# Run with coverage
pytest --cov=app --cov-report=html
# Parallel execution
pytest -n auto # Requires pytest-xdist# Stop on first failure
pytest -x
# Show local variables on failure
pytest -l
# Run last failed tests
pytest --lf
# Run failed tests first
pytest --ff
pytest.ini Template
[pytest]# Minimum pytest versionminversion = 7.0# Test discovery patternspython_files = test_*.py *_test.py
python_classes = Test*
python_functions = test_*
# Test pathstestpaths = tests
# Command line optionsaddopts =
-v
--strict-markers
--cov=app
--cov-report=html
--cov-report=term-missing
--cov-fail-under=80# Markersmarkers =
unit: Unit tests
integration: Integration tests
slow: Slow-running tests
smoke: Smoke tests for critical paths
# Django settings (if using Django)DJANGO_SETTINGS_MODULE = myproject.settings
# Asyncio modeasyncio_mode = auto
systematic-debugging: Root cause investigation for failing tests
Quick TDD Workflow Reference (Inlined for Standalone Use)
RED → GREEN → REFACTOR Cycle:
RED Phase: Write Failing Test
deftest_should_authenticate_user_when_credentials_valid():
# Test that describes desired behavior
user = User(username='alice', password='secret123')
result = authenticate(user)
assert result.is_authenticated isTrue# This test will fail because authenticate() doesn't exist yet
defauthenticate(user):
# Clean up while keeping tests green
hashed_password = hash_password(user.password)
stored_user = database.get_user(user.username)
return AuthResult(
is_authenticated=(stored_user.password_hash == hashed_password)
)
Test Structure: Arrange-Act-Assert (AAA)
deftest_user_creation():
# Arrange: Set up test data
user_data = {'username': 'alice', 'email': 'alice@example.com'}
# Act: Perform the action
user = create_user(user_data)
# Assert: Verify outcomeassert user.username == 'alice'assert user.email == 'alice@example.com'
Quick Debugging Reference (Inlined for Standalone Use)
Phase 1: Root Cause Investigation
Read error messages completely (stack traces, line numbers)
Reproduce consistently (document exact steps)
Check recent changes (git log, git diff)
Understand what changed and why it might cause failure
Phase 2: Isolate the Problem
# Use pytest's built-in debugging
pytest tests/test_auth.py -vv --pdb # Drop into debugger on failure
pytest tests/test_auth.py -x # Stop on first failure
pytest tests/test_auth.py -k "auth"# Run only auth-related tests# Add strategic print/loggingdeftest_complex_workflow():
user = create_user({'username': 'test'})
print(f"DEBUG: Created user {user.id}") # Visible with pytest -s
result = process_user(user)
print(f"DEBUG: Result status {result.status}")
assert result.success
Phase 3: Fix Root Cause
Fix the underlying problem, not symptoms
Add regression test to prevent recurrence
Verify fix doesn't break other tests
Phase 4: Verify Solution
# Run full test suite
pytest
# Run with coverage
pytest --cov=src --cov-report=html
# Verify specific test patterns
pytest -k "auth or login" -v
[Full TDD and debugging workflows available in respective skills if deployed together]
Python Code-Quality Anti-Patterns
Clean code under test is easier to test, and several Python quality defects directly
cause flaky or silently-passing tests (overly broad except, malformed exception
classes, identity-vs-equality bugs). Code-quality anti-patterns now live in their own
dedicated skill rather than this testing skill:
See the python-code-quality skill (toolchains/python/quality/code-quality) for
the highest-value Python anti-patterns — exception-hierarchy correctness, singleton
identity comparison, narrow exception handling, wildcard-import avoidance, magic-number
naming, and dead-local removal — with non-compliant vs compliant examples and how to
test each. For the project-wide severity-tagged review checklist, see the
code-review-standards skill.
pytest Version Compatibility: This skill covers pytest 7.0+ and reflects current best practices for Python testing in 2025.