Why pytest?
Python ships with a built-in testing module called unittest, but most modern Python developers choose pytest instead. It's simpler to write, produces cleaner output, and has a powerful plugin ecosystem. If you're writing tests in Python today, pytest is the de facto standard.
Installation
pip install pytest
Verify your installation:
pytest --version
Your First Test
Create a file named test_math.py. pytest automatically discovers files and functions that match naming conventions:
def add(a, b):
return a + b
def test_add_positive_numbers():
assert add(2, 3) == 5
def test_add_with_zero():
assert add(0, 10) == 10
def test_add_negative_numbers():
assert add(-1, -1) == -2
Run your tests:
pytest test_math.py
Test Discovery Rules
- Files must be named
test_*.pyor*_test.py - Test functions must start with
test_ - Test classes must start with
Test(no__init__needed)
Fixtures: Reusable Test Setup
Fixtures are one of pytest's most powerful features. They provide setup logic that can be shared across multiple tests:
import pytest
@pytest.fixture
def sample_user():
return {"name": "Alice", "age": 30, "email": "alice@example.com"}
def test_user_name(sample_user):
assert sample_user["name"] == "Alice"
def test_user_age(sample_user):
assert sample_user["age"] == 30
Fixtures can also handle teardown using yield:
@pytest.fixture
def db_connection():
conn = create_db_connection()
yield conn
conn.close() # Teardown runs after the test
Parametrize: Test Multiple Inputs Cleanly
Instead of writing a separate test for each input, use @pytest.mark.parametrize:
import 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
Testing Exceptions
Use pytest.raises to assert that specific exceptions are raised:
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
def test_divide_by_zero():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(10, 0)
Useful Command-Line Options
| Option | Effect |
|---|---|
-v | Verbose output (shows each test name) |
-x | Stop after first failure |
-k "pattern" | Run tests matching a name pattern |
--tb=short | Shorter traceback format |
--cov | Measure code coverage (requires pytest-cov) |
Next Steps
Once you're comfortable with the basics, explore these pytest plugins:
- pytest-cov — code coverage reporting
- pytest-mock — easy mocking integration
- pytest-xdist — parallel test execution
- pytest-asyncio — testing async code
Testing is an investment that pays dividends every time you refactor or ship new features. Start with pytest and build the habit early.