Verify behaviour with regression tests in milliseconds, living alongside the code.
Tests are a smoke alarm: cheap, automatic, loud when something is wrong.
test_xxx.py
def test_add():
assert add(2, 3) == 5
pytest collects test_*, runs in
isolation, captures stdout, uses
fixtures + parametrize; fast (~ms).
pytest discovers test_*.py and test_* functions; assert statements compare actual vs expected, with rich rewrite. Fixtures (@pytest.fixture) provide setup/teardown. @pytest.mark.parametrize runs the same test over many inputs. Keep tests fast (<100ms each), deterministic (no real network), and isolated (no shared global state).
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
simplest pytest test; assertions rewrite for rich failure output.
import pytest
@pytest.fixture
def db():
import sqlite3
conn = sqlite3.connect(':memory:')
yield conn
conn.close()
def test_insert(db):
db.execute('CREATE TABLE t (n INT)')
db.execute('INSERT INTO t VALUES (1)')
assert db.execute('SELECT n FROM t').fetchone()[0] == 1
fixture provides per-test setup/teardown; yield so teardown runs even on failure.
import pytest
@pytest.mark.parametrize('a,b,expect', [
(1,1,2), (0,0,0), (-1,1,0), (100,200,300),
])
def test_add_param(a, b, expect):
assert add(a, b) == expect
parametrize reduces 4 tests to 1; pytest reports each row separately.
Tests hitting real external services without a fake - flaky, slow, broken in CI. Wrap services in interfaces; inject fakes for unit tests. Asserting on stdout/print output instead of return values.
One assertion per concept; permissive fixtures (tmp_path, monkeypatch, capsys); parametrize for combinatorial coverage; markers for slow/integration; keep tests fast and isolated.
test_external_api flaking.requests.get(real_url) and asserted on real response.pytest-httpserver or responses; if real, short timeout + flaky reruns=3.make test-int.