Covers pytest as the default, a how-to-choose item per README subcategory, and a guide on Hypothesis, Playwright, tox/Nox, mocks, and coverage. Co-Authored-By: Claude <noreply@anthropic.com>
6.7 KiB
Plain assert statements and fixtures make pytest the Python testing framework for new code. Add Hypothesis for property-based tests, Playwright for browsers.
How to choose:
- Writing tests: pytest, plus Hypothesis for edge cases; Robot Framework for non-programmers
- Tests across Python versions: tox, or Nox to configure them in Python
- Browser tests: Playwright; Selenium or SeleniumBase for WebDriver suites
- Load tests written in Python: Locust
- Tests generated from an OpenAPI or GraphQL schema: Schemathesis
- Mocking: unittest.mock; responses, RESPX, or VCR.py for HTTP; FreezeGun for time
- Test objects: factory_boy for ORM models, Polyfactory for type hints
- Code coverage: Coverage.py
- Fake data: Faker or Mimesis
pytest lets you write tests with plain assert statements and shows you what failed. It also runs your unittest suites as they are, so you can move an old suite over bit by bit. Share setup through fixtures: use yield fixtures for teardown, and put the ones several test modules need in conftest.py. For a new project, pytest's docs recommend a src layout and the importlib import mode.
Hypothesis adds property-based tests to pytest or unittest. You describe the inputs with a strategy passed to @given, and Hypothesis picks which ones to try, including edge cases you didn't think of. It's an addition to unit tests, not always a replacement: start with round trips like encode/decode, and with tests you already parametrize. Use the most general strategy your test should pass for.
Robot Framework is a keyword-driven framework for acceptance testing. Tests are tables of keywords, and you build higher-level keywords out of existing ones. That suits teams where people who don't write Python read or write the tests.
tox and Nox both run your tests in separate virtual environments, one per Python version or task. Configure tox in TOML, in tox.toml or pyproject.toml. It tests the installed package, not your checkout, so it catches packaging mistakes. Nox is configured in Python, in a noxfile.py, and tox's own docs point you to it if tox configuration is too limiting.
Playwright was created for end-to-end testing and runs Chromium, Firefox, and WebKit. Write your tests with its pytest plugin, which gives each test its own browser context. Playwright waits for elements to be ready before each action, so you don't add waits yourself. Find elements by role, text, or test id rather than CSS or XPath, which break when the page changes.
Selenium drives real browsers through WebDriver, on your machine or on remote ones through Selenium Grid. Keep it for the WebDriver suites you already have. It doesn't structure your test suite for you, so run it under a test runner like pytest, and use explicit waits for the exact condition you need. SeleniumBase builds on Selenium's WebDriver APIs and runs under pytest, and its methods wait for elements that need time to load.
For load tests, Selenium's docs advise against using it; use Locust. You write the tests in regular Python code: a User class with @task methods. When you need more load, run one worker per CPU core.
Schemathesis generates property-based tests from your OpenAPI or GraphQL schema, using Hypothesis under the hood. Its docs recommend the CLI for most users, since the pytest integration has fewer features.
unittest.mock ships with Python. Patch where an object is looked up, not where it's defined, and add autospec=True so your tests fail when the real API changes. For code you own, pytest's docs suggest you pass dependencies in rather than patch them.
For HTTP, pick the mock that matches your client: responses for requests, and RESPX for HTTPX. Both raise an error on requests you didn't mock. VCR.py records real responses to a cassette file and replays them, with many clients including both. Filter out credentials before you commit cassettes. For time, FreezeGun freezes datetime and time at the moment you choose.
factory_boy replaces static fixtures with factories that set only the fields a test cares about, and works with Django, SQLAlchemy, and MongoDB models. Polyfactory builds objects from type hints: dataclasses, TypedDicts, Pydantic models, and more.
For the data itself, Faker generates localized fake data and comes with a pytest fixture; factory_boy uses it too. Mimesis is fully typed and generates data from schemas, in many languages.
Coverage.py measures which lines your tests run. Run pytest under it with coverage run -m pytest, which its docs say is enough for most purposes, and include your tests in the measurement. It measures lines by default; add --branch to see which branches never ran.
Write each test so it runs in any order, without relying on other tests. A flaky test usually means state the test doesn't control, and random data is one such state: seed Faker, Mimesis, factory_boy, and Polyfactory so a failing build reproduces.