Learning Goals
3 min- Skip tests unconditionally and conditionally (
skip,skipif). - Mark known-broken tests with
xfail— honestly. - Define and register custom markers (
@pytest.mark.slow). - Select tests by marker with
-m.
Warm-Up · Don't Delete, Mark
5 minimport pytest @pytest.mark.skip(reason="API endpoint not built yet") def test_future_feature(): ... @pytest.mark.xfail(reason="known rounding bug, ticket #42") def test_known_bug(): assert round_money(0.125) == 0.13 # currently returns 0.12
When a test can't pass yet, don't comment it out or delete it (it'll be forgotten). skip says "don't run this here"; xfail says "we know this fails — track it". Both keep the test visible and honest in your suite.
New Concept · The Markers
14 minskip — never run this one
@pytest.mark.skip(reason="needs hardware not in CI") def test_printer(): ...
skipif — run only under some condition
import sys, pytest @pytest.mark.skipif(sys.platform == "win32", reason="POSIX-only feature") def test_unix_paths(): ... @pytest.mark.skipif(sys.version_info < (3, 11), reason="needs Python 3.11+") def test_new_syntax(): ...
xfail — expected to fail
@pytest.mark.xfail(reason="bug #42, not fixed yet") def test_buggy(): assert broken_function() == "correct"
x xfailed — failed as expected (counts as "ok-ish") X XPASS — UNEXPECTEDLY passed → the bug got fixed! remove the marker.
When an xfail test starts passing (XPASS), pytest flags it — your reminder that the bug is fixed and the marker should go.
Custom markers — tag & select
@pytest.mark.slow def test_full_import(): ... @pytest.mark.smoke def test_app_starts(): ...
# pyproject.toml — register markers (avoids warnings) [tool.pytest.ini_options] markers = [ "slow: takes more than a second", "smoke: quick health checks", ]
pytest -m smoke # run ONLY smoke tests (fast feedback) pytest -m "not slow" # skip the slow ones during development pytest -m "slow" # run only the slow ones (e.g. nightly CI)
Worked Example · A Marked Suite
12 min# test_app.py import sys, pytest from app import startup, full_sync, export_report @pytest.mark.smoke def test_app_starts(): assert startup() == "ok" @pytest.mark.slow def test_full_sync(): result = full_sync() # imagine this takes 30 seconds assert result.count > 0 @pytest.mark.skipif(sys.platform == "win32", reason="uses fork()") def test_parallel_export(): assert export_report(parallel=True) @pytest.mark.xfail(reason="unicode edge case, ticket #88") def test_export_emoji_title(): assert export_report(title="🚀 launch") is True
# during development — fast loop, skip the 30s test: $ pytest -m "not slow" -v test_app_starts PASSED test_parallel_export PASSED (or SKIPPED on Windows) test_export_emoji_title XFAIL (bug #88) # quick health check before a demo: $ pytest -m smoke test_app_starts PASSED # nightly CI runs everything including slow: $ pytest -v
Read the diff
Markers turn one suite into views: developers run -m "not slow" for a snappy loop; -m smoke before a demo; CI runs the lot. The xfail keeps the unicode bug visible without breaking the build, and the skipif documents why a test doesn't run on Windows. Honest, organised, and flexible.
Try It Yourself
13 minMark a test @pytest.mark.skip(reason=...). Run pytest and confirm it shows as skipped (s) with your reason in -rs output.
Write a failing test, mark it xfail (it shows x). Fix the code so it passes — confirm pytest now reports XPASS, nudging you to remove the marker.
Register a slow and a smoke marker in pyproject.toml, tag a few tests, and run each subset with -m.
Mini-Challenge · Two-Speed Suite
8 minBuild a suite with at least 6 tests: tag the quick ones smoke and the heavy ones slow (simulate with time.sleep). Provide the two commands a developer would use: a fast inner-loop command and a full pre-merge command. Time both.
Show the commands
# inner loop (fast): skip slow tests pytest -m "not slow" -q # pre-merge (thorough): everything pytest -q # pyproject.toml [tool.pytest.ini_options] markers = ["slow: long-running", "smoke: quick checks"]
Non-negotiables: register markers (no warnings), demonstrate the time difference between the fast and full runs.
Recap
3 minskip/skipif exclude tests that can't/shouldn't run (always with a reason). xfail marks known failures honestly — and warns (XPASS) when they're fixed. Custom markers (registered in pyproject.toml) tag tests so -m can run subsets like smoke or not slow. Never comment out a test — mark it. Next: your first real testing project.
Vocabulary Card
- skip / skipif
- Exclude a test always / under a condition.
- xfail
- Mark a test as expected to fail; XPASS warns when it unexpectedly passes.
- custom marker
- A tag (
@pytest.mark.slow) for grouping/selecting tests. - -m selector
- Run tests by marker expression, e.g.
-m "not slow".
Homework
4 minTake an existing suite and add: one skipif (platform or version), one xfail for a real or simulated known bug, and a slow/smoke marker split. Document the developer commands for fast vs full runs in your README.
Use the marked-suite worked example as the model, plus the two-speed commands from the mini-challenge. Register all custom markers in pyproject.toml.