pytest is the standard Python test runner, and its zero-boilerplate style is why most projects actually write tests.
pytest finds and runs tests with almost no ceremony. Install it in your virtual environment (pip install pytest) and write a file whose name starts with test_ (or ends with _test.py). Inside, any function whose name starts with test is a test. You check results with the plain assert statement; no self.assertEqual needed.
Run pytest in the project root. It discovers test files recursively, imports them, runs every test function and prints a dot per pass, F per failure and E per error (an exception outside the assert, e.g. in setup). The summary line says how many passed and failed, and the exit code is non-zero on failure, so CI can rely on it.
Tests can also be methods on classes named Test... (without an __init__), which is just a way to group related tests; no base class is required.
Good first habits:
- One behavior per test, named for that behavior:
test_total_includes_tax. - Arrange, act, assert: set up inputs, call the code, check the outcome.
- Tests must not depend on each other or on execution order.
Keep tests in a tests/ directory next to your package, and import the code under test like any user would. Use pytest -q for quiet output or -v to see each test name.
Example
# test_cart.py
def cart_total(prices, tax=0.2):
return round(sum(prices) * (1 + tax), 2)
def test_empty_cart_is_zero():
assert cart_total([]) == 0
def test_total_includes_tax():
assert cart_total([10, 5]) == 18.0
class TestDiscounts:
def test_zero_tax(self):
assert cart_total([3, 4], tax=0) == 7
# $ pytest -q
# ... [100%]
# 3 passed in 0.01sRemember
- Files test_*.py, functions test_*, classes Test*
- Use plain assert for checks
- Run pytest; non-zero exit code on failure
- Tests must be independent of each other and of order
Watch out for
- Naming a file tests.py or a function check_total, so pytest never collects it.