Learning Goals
3 min- Know pytest's discovery rules for files, functions and classes.
- Lay out a project with a
tests/folder. - Use
conftest.pyfor shared setup. - Configure pytest in
pyproject.toml/pytest.ini.
Warm-Up · The Rules
5 minfiles test_*.py OR *_test.py functions test_* classes Test* (no __init__, no TestCase needed) methods test_* inside a Test* class
pytest finds tests by these naming patterns. If a test isn't running, 95% of the time it's a name that doesn't match: check_login instead of test_login, or tests.py instead of test_x.py. Memorise the patterns.
New Concept · Conventions & Config
14 minPlain functions OR Test classes
# both are discovered by pytest: def test_add(): # plain function assert add(2, 3) == 5 class TestMath: # class — name starts with "Test", NO __init__ def test_add(self): assert add(2, 3) == 5 def test_sub(self): assert sub(5, 2) == 3
pytest classes group related tests but (unlike unittest) need no base class and no __init__.
The standard layout
myproject/ ├─ src/myproject/ │ ├─ __init__.py │ └─ pricing.py ├─ tests/ │ ├─ test_pricing.py │ └─ conftest.py ← shared fixtures (next lessons) └─ pyproject.toml ← pytest config
conftest.py — shared setup, auto-loaded
# tests/conftest.py — pytest imports this automatically import pytest @pytest.fixture def sample_user(): return {"name": "Aisyah", "age": 13} # any test in this folder can just ask for 'sample_user' as an argument
conftest.py is special: pytest loads it automatically and shares its fixtures with every test in that directory. No imports needed. (Fixtures themselves are Lessons 14-15.)
Config in pyproject.toml
# pyproject.toml [tool.pytest.ini_options] testpaths = ["tests"] addopts = "-v --strict-markers" python_files = "test_*.py"
This tells pytest where to look and which flags to always apply — so a bare pytest just works for everyone on the team.
The import gotcha
Two test files both named test_utils.py in different folders without __init__.py → pytest errors with "import file mismatch". Fix: add __init__.py files, or use unique test file names.
Worked Example · A Configured Project
12 minshop/ ├─ shop/ │ ├─ __init__.py │ └─ cart.py ├─ tests/ │ ├─ conftest.py │ └─ test_cart.py └─ pyproject.toml
# tests/conftest.py import pytest from shop.cart import Cart @pytest.fixture def empty_cart(): return Cart() @pytest.fixture def cart_with_items(): c = Cart() c.add("roti", 2, 1.50) c.add("milo", 1, 3.00) return c
# tests/test_cart.py — fixtures arrive as arguments def test_new_cart_is_empty(empty_cart): assert empty_cart.total() == 0 def test_cart_total(cart_with_items): assert cart_with_items.total() == 6.00 class TestCartEdgeCases: def test_remove_missing_item(self, cart_with_items): import pytest with pytest.raises(KeyError): cart_with_items.remove("nasi")
$ pytest # config + conftest mean a bare command just works tests/test_cart.py::test_new_cart_is_empty PASSED tests/test_cart.py::test_cart_total PASSED tests/test_cart.py::TestCartEdgeCases::test_remove_missing_item PASSED 3 passed
Read the diff
The fixtures live in conftest.py and tests just name them as arguments — pytest injects them. The pyproject.toml config means a teammate types pytest and everything runs the same way. Both a plain function and a Test* class coexist. This is the canonical professional layout.
Try It Yourself
13 minWrite a test as check_foo; run pytest and confirm it's not collected. Rename to test_foo; confirm it runs.
Add a conftest.py with one fixture. Use it in two test files without importing it. Confirm both work.
Add a [tool.pytest.ini_options] section to pyproject.toml with testpaths and addopts = "-v". Confirm a bare pytest now runs verbose from the tests folder.
Mini-Challenge · Diagnose the Silent Test
8 minHere's a file where one test never runs. Find why (there are two issues):
# tests.py ← problem 1: file not named test_*.py class CartTests: ← problem 2: class doesn't start with "Test" def test_total(self): assert Cart().total() == 0
Show the fix
# rename file → test_cart.py # rename class → TestCart class TestCart: def test_total(self): assert Cart().total() == 0
Both the file (test_*.py) and the class (Test*) must match the conventions. Run pytest --collect-only to see exactly what pytest found.
Recap
3 minpytest discovers test_*.py files, test_* functions, and methods inside Test* classes (no base class needed). Keep tests in tests/; put shared fixtures in conftest.py (auto-loaded); configure once in pyproject.toml. When a test doesn't run, check the names first — use pytest --collect-only to see what was found. Next: the smart assert introspection in detail.
Vocabulary Card
- conftest.py
- Auto-loaded file sharing fixtures across a directory's tests.
- collection
- pytest's process of discovering which tests to run.
- pyproject.toml
- Where pytest (and other tools) read their configuration.
- --collect-only
- Lists discovered tests without running them — great for debugging discovery.
Homework
4 minSet up a project the canonical way: a package folder, a tests/ folder with a conftest.py (≥ 1 fixture) and 2 test files, and a pyproject.toml pytest config. Confirm a bare pytest runs everything green, and pytest --collect-only shows all your tests.
Follow the worked-example shop/ layout. The pyproject.toml needs [tool.pytest.ini_options] with testpaths = ["tests"].