
pytest fixture
pytest fixture
pytest parametrize
pytest parametrize
pytest.raises
pytest.raises
test discovery
test discovery
I spent way too long wondering why my tests were "passing" when my code was definitely broken. Pytest is incredibly powerful, but it quietly lets you get away with bad habits early on.
Tests should fail when the code is broken. If you write a test and you haven't seen it fail on purpose at least once, don't trust it.
Install with pip install pytest. Run it by typing pytest in your project root. It will automatically find files named test_*.py.
Pytest doesn't require a heavy DSL. Just write functions starting with test_ and use plain Python assert statements.
def test_addition():
assert 1 + 1 == 2
Speed up your feedback loop by running only the specific test you are working on:
pytest test_math.py::test_addition
How do you run a single specific test in pytest?
Use fixtures to set up database connections or dummy data once, instead of copy-pasting it into every test.
@pytest.fixture
def empty_cart():
return ShoppingCart()
def test_add_item(empty_cart):
empty_cart.add("apple")
assert len(empty_cart) == 1
| Fixture Scope | When it Runs | Use Case |
|---|---|---|
| function (default) | Before each test function | Isolated test data |
| class | Once per test class | Shared setup for class tests |
| module | Once per test file | Expensive DB connections |
| session | Once per test session | Global config, expensive fixtures |
Stop writing test_add_positive, test_add_negative. Use parametrization to run the same logic with different inputs.
@pytest.mark.parametrize("a,b,expected", [(1,2,3), (-1,1,0)])
def test_add(a, b, expected):
assert add(a, b) == expected
What does @pytest.mark.parametrize create?
Don't use try/except to test for errors. Use pytest.raises to guarantee an exception is thrown.
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
divide(1, 0)
Why prefer `pytest.raises` over `try/except` for testing exceptions?
Use tmp_path fixture (built-in) for isolated test files:
def test_write_read(tmp_path):
file = tmp_path / "data.txt"
file.write_text("hello")
assert file.read_text() == "hello"
Built-in fixture for patching without external library:
def test_api_call(monkeypatch):
monkeypatch.setattr("myapp.api.get", lambda: {"status": "ok"})
result = myapp.api.get()
assert result["status"] == "ok"
Mark tests and run subsets:
# In pytest.ini or pyproject.toml
markers = [
"slow: marks tests as slow",
"integration: marks tests as integration tests"
]
# In test file
@pytest.mark.slow
def test_heavy_computation():
...
# Run only slow tests
pytest -m slow
# Exclude slow tests
pytest -m "not slow"
| Goal | Use | Example |
|---|---|---|
| Shared test data | <code>@pytest.fixture</code> | DB connections, config objects |
| Multiple inputs, same logic | <code>@pytest.mark.parametrize</code> | Edge cases, boundary values |
| Assert exception raised | <code>pytest.raises(Error)</code> | Division by zero, invalid input |
| Isolate filesystem operations | <code>tmp_path</code> fixture | File read/write tests |
| Patch external dependency | <code>monkeypatch</code> fixture | API calls, environment vars |
| Run subset of tests | <code>@pytest.mark.slow</code> + <code>pytest -m slow</code> | CI separation, dev feedback |
You want to test a function with inputs: (2,3)=5, (0,0)=0, (-1,1)=0, (100,200)=300. What's the cleanest approach?
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Design clean test suites with pytest fixture scoping, indirect parameterization, dynamic generator hooks, and resilient cleanup lifecycle patterns.
Read more
Master foundational Python paradigms: memory references, mutable vs immutable types, scoping rules, generators, and clean object-oriented patterns.
Read more
A practical collection of Python techniques that reduce boilerplate, prevent bugs, and make everyday code cleaner - selected from real codebases, not tutorial fantasy.
Read more