8 minute read


pytest is a powerful testing framework for Python that makes it easy to write simple and scalable test cases.

pip install pytest

Using Assertions

def test_addition():
    assert 1 + 1 == 2
    assert "pytest" in "I love pytest"
    assert not False

def test_string():
    assert "pytest" in "I love pytest"

def test_exception():
    with pytest.raises(ZeroDivisionError):
        1 / 0

def test_custom_message():
    assert 1 + 1 == 3, "Math is broken!"
    

Parameterized Tests

# test_example.py
import pytest

@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),
    (2, 3, 5),
    (3, 4, 7),
])
def test_add(a, b, expected):
    assert a + b == expected

Running Specific Test

pytest test_example.py
pytest test_example.py::test_add

Run tests with verbose output:

pytest -v

Stop after the first N failures:

pytest --maxfail=2

Run tests in parallel (requires pytest-xdist):

pip install pytest-xdist
pytest -n 4

Test Discovery

pytest automatically discovers tests that:

  • Are in files named test_*.py or *_test.py
  • Contain functions starting with test_

Fixtures

Create reusable test setup with fixtures:

import pytest

@pytest.fixture
def sample_data():
    return [1, 2, 3]

def test_sum(sample_data):
    assert sum(sample_data) == 6

Markers

Skip a test:

import pytest

@pytest.mark.skip(reason="Not implemented yet")
def test_not_implemented():
    pass

Conditionally skip a test:

@pytest.mark.skipif(condition, reason="Reason")
def test_skip_if():
    pass

Mark a test as expected to fail:

@pytest.mark.xfail(reason="Bug 123")
def test_known_bug():
    assert False

Running Tests with Markers

Run tests with a specific marker:

pytest -m marker_name

Custom Markers

Define custom markers in pytest.ini:

[pytest]
markers =
    slow: marks tests as slow

Use custom markers in tests:

@pytest.mark.slow
def test_slow_function():
    pass

Generating Reports

Generate a detailed HTML report (requires pytest-html):

pip install pytest-html
pytest --html=report.html

Generate a JUnit XML report for CI integration:

pytest --junitxml=report.xml

Debugging Tests

Run tests with Pythonโ€™s built-in debugger:

pytest --pdb

Use a breakpoint in the code:

def test_debug():
    breakpoint()  # or use `import pdb; pdb.set_trace()` for older versions
    assert 1 + 1 == 2

Coverage Reports

Measure test coverage (requires pytest-cov):

pip install pytest-cov
pytest --cov=your_module tests/

Generate an HTML coverage report:

pytest --cov=your_module --cov-report=html

Test Directory Structure

Typical project structure:

my_project/
    src/
        my_module.py
    tests/
        test_my_module.py

Example Test File

A complete example test file:

# tests/test_my_module.py

import pytest
from src.my_module import add, subtract, multiply, divide

@pytest.fixture
def sample_numbers():
    return (10, 5)

def test_add(sample_numbers):
    a, b = sample_numbers
    assert add(a, b) == 15

def test_subtract(sample_numbers):
    a, b = sample_numbers
    assert subtract(a, b) == 5

def test_multiply(sample_numbers):
    a, b = sample_numbers
    assert multiply(a, b) == 50

def test_divide(sample_numbers):
    a, b = sample_numbers
    assert divide(a, b) == 2
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        divide(a, 0)