Master pytest testing framework with simple syntax, assertions, fixtures, parametrization, and best practices for writing robust Python tests.
📌 Python pytest, testing framework, pytest tutorial, test automation, pytest fixtures, pytest assertions
pytest is a popular Python testing framework emphasizing simplicity and accessibility. Unlike verbose xUnit frameworks, pytest uses standard Python functions with plain assert statements—making tests natural and readable.
pytest offers five key advantages: simple syntax using standard assert statements, automatic test discovery (finds test_*.py files), informative failure output with detailed debugging info, rich plugin ecosystem, and low entry barrier for beginners.
Installation is straightforward: pip install pytest. Tests are discovered automatically in files named test_*.py or *_test.py, and in functions/test methods prefixed with test_. No explicit registration required—pytest finds everything automatically.
Running tests offers flexibility: pytest alone runs all tests, pytest -v provides verbose output, and pytest test_example.py -v targets a specific file. Success appears as dots (.) or 'PASSED', failures show detailed tracebacks with assertion comparisons.
Test organization can use classes starting with 'Test' (no inheritance required) for grouping related tests. pytest automatically discovers these test methods without any special base classes or setup methods.
# test_example.py
def add(x, y):
return x + y
def test_add_positive_numbers():
assert add(1, 2) == 3
def test_add_negative_numbers():
assert add(-1, -2) == -3
def test_add_zero():
assert add(5, 0) == 5
# Run with: pytest test_example.py -vdef divide(x, y):
if y == 0:
raise ValueError("Cannot divide by zero")
return x / y
def test_divide_by_zero():
with pytest.raises(ValueError) as exc_info:
divide(10, 0)
assert str(exc_info.value) == "Cannot divide by zero"
def test_divide_normal():
assert divide(10, 2) == 5class Calculator:
def add(self, x, y):
return x + y
def multiply(self, x, y):
return x * y
class TestCalculator:
def test_add(self):
calc = Calculator()
assert calc.add(2, 3) == 5
assert calc.add(-1, 1) == 0
def test_multiply(self):
calc = Calculator()
assert calc.multiply(3, 4) == 12
assert calc.multiply(-2, 5) == -10
# No inheritance needed - just use 'Test' prefiximport pytest
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300),
])
def test_add(a, b, expected):
assert add(a, b) == expected
# Runs the same test 4 times with different inputsimport pytest
@pytest.fixture
def sample_data():
"""Setup fixture that provides test data"""
return {"name": "Test", "value": 42}
@pytest.fixture
def database():
"""Setup/teardown fixture for database"""
db = {"data": []}
yield db # This is provided to the test
# Teardown: cleanup code here
db.clear()
def test_with_fixture(sample_data):
assert sample_data["name"] == "Test"
assert sample_data["value"] == 42
def test_database_operations(database):
database["data"].append("item1")
assert len(database["data"]) == 1import pytest
class TestDatabase:
@pytest.fixture(autouse=True)
def setup_teardown(self):
# Setup: runs before each test
self.db = []
yield
# Teardown: runs after each test
self.db.clear()
def test_insert(self):
self.db.append("item1")
assert len(self.db) == 1
def test_delete(self):
self.db.append("item1")
self.db.remove("item1")
assert len(self.db) == 0# Run all tests in directory pytest # Run with verbose output pytest -v # Run specific file pytest test_calculator.py -v # Run specific test function pytest test_calculator.py::test_add -v # Run tests matching pattern pytest -k "add" -v # Runs tests containing "add" # Stop on first failure pytest -x # Show print statements pytest -s