Introduction
pytest provides several ways to disable (skip) tests: @pytest.mark.skip unconditionally skips a test, @pytest.mark.skipif skips based on a condition, pytest.importorskip skips if a module is missing, and @pytest.mark.xfail marks a test as expected to fail without blocking the test suite. Skipped tests appear as s in the output and are counted separately from passes and failures. These markers are essential for handling platform-specific tests, optional dependencies, and known bugs.
Unconditional Skip
1import pytest
2
3@pytest.mark.skip(reason="Not implemented yet")
4def test_future_feature():
5 assert some_function() == expected_value
6
7# Skip an entire class
8@pytest.mark.skip(reason="Refactoring in progress")
9class TestOldModule:
10 def test_one(self):
11 pass
12
13 def test_two(self):
14 pass
@pytest.mark.skip permanently disables the test. Always provide a reason so teammates know why it was skipped. The reason appears in the output with -v or -rs flags.
Conditional Skip
1import sys
2import pytest
3
4# Skip on specific Python version
5@pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+")
6def test_match_statement():
7 match value:
8 case 1:
9 assert True
10
11# Skip on specific platform
12@pytest.mark.skipif(sys.platform == "win32", reason="Unix-only test")
13def test_unix_permissions():
14 import os
15 assert os.access("/tmp", os.W_OK)
16
17# Skip based on environment variable
18import os
19@pytest.mark.skipif(
20 os.environ.get("CI") != "true",
21 reason="Only runs in CI environment"
22)
23def test_integration():
24 pass
25
26# Multiple conditions
27@pytest.mark.skipif(
28 sys.platform == "darwin" and sys.version_info < (3, 9),
29 reason="Known issue on macOS with Python < 3.9"
30)
31def test_macos_specific():
32 pass
skipif evaluates the condition at collection time. If true, the test is skipped with the given reason.
Skip if Module Not Available
1import pytest
2
3# Skip if the optional dependency is not installed
4np = pytest.importorskip("numpy")
5pd = pytest.importorskip("pandas", minversion="1.5.0")
6
7def test_numpy_calculation():
8 arr = np.array([1, 2, 3])
9 assert np.sum(arr) == 6
10
11def test_pandas_dataframe():
12 df = pd.DataFrame({"a": [1, 2, 3]})
13 assert len(df) == 3
14
15# Module-level skip — skips the entire file
16pytest.importorskip("some_optional_lib")
importorskip attempts to import the module and skips all tests in the file (or test) if it fails. The minversion parameter checks the module's __version__.
Expected Failure (xfail)
1import pytest
2
3@pytest.mark.xfail(reason="Known bug #1234")
4def test_known_bug():
5 assert broken_function() == expected # Test runs but failure is expected
6
7# xfail with condition
8@pytest.mark.xfail(
9 sys.platform == "win32",
10 reason="Windows file locking issue"
11)
12def test_file_operation():
13 pass
14
15# Strict xfail — fails if the test unexpectedly passes
16@pytest.mark.xfail(strict=True, reason="Should not pass until bug #1234 is fixed")
17def test_strict_xfail():
18 assert broken_function() == expected
xfail lets the test run. If it fails, it is reported as xfail (expected). If it unexpectedly passes, it is reported as xpass. With strict=True, an unexpected pass counts as a test failure.
Skip at Runtime
1import pytest
2
3def test_database_connection():
4 db = connect_to_database()
5 if db is None:
6 pytest.skip("Database not available")
7 assert db.is_connected()
8
9def test_with_external_service():
10 try:
11 response = requests.get("https://api.example.com/health", timeout=2)
12 except requests.ConnectionError:
13 pytest.skip("External service unreachable")
14 assert response.status_code == 200
pytest.skip() called inside a test body skips at runtime after the test has already started. Use this when the skip condition can only be determined during execution.
Skipping in Fixtures
1import pytest
2
3@pytest.fixture
4def database():
5 try:
6 db = connect_to_database()
7 except ConnectionError:
8 pytest.skip("Database not available")
9 yield db
10 db.close()
11
12def test_query(database):
13 # Skipped if the database fixture calls pytest.skip()
14 result = database.execute("SELECT 1")
15 assert result == 1
When a fixture calls pytest.skip(), all tests that use that fixture are skipped.
Custom Skip Markers
1# conftest.py
2import pytest
3
4# Register custom markers
5def pytest_configure(config):
6 config.addinivalue_line("markers", "slow: marks tests as slow")
7 config.addinivalue_line("markers", "integration: marks integration tests")
8
9# pytest.ini or pyproject.toml
10# [tool.pytest.ini_options]
11# markers = [
12# "slow: marks tests as slow",
13# "integration: marks integration tests",
14# ]
1# test_example.py
2import pytest
3
4@pytest.mark.slow
5def test_heavy_computation():
6 pass
7
8@pytest.mark.integration
9def test_api_call():
10 pass
1# Run only fast tests (exclude slow)
2pytest -m "not slow"
3
4# Run only integration tests
5pytest -m integration
6
7# Exclude multiple markers
8pytest -m "not slow and not integration"
Custom markers combined with -m let you selectively run or skip categories of tests without modifying the test code.
Common Pitfalls
Skipping without a reason: @pytest.mark.skip without reason makes it unclear why the test was disabled. Always provide a reason so the skip can be revisited later.
Using skip instead of xfail for known bugs: skip hides the test entirely. xfail runs the test and alerts you when the bug is fixed (the test unexpectedly passes). Use xfail for known bugs.
Forgetting to re-enable skipped tests: Skipped tests accumulate over time. Regularly audit tests marked with skip and skipif to check whether the conditions still apply.
skipif condition evaluated at import time: The condition in @pytest.mark.skipif runs when the module is imported. Side effects in the condition (like network calls) slow down test collection for all tests.
Commenting out tests instead of using skip: Commented-out tests are invisible in test reports. Use @pytest.mark.skip so the test appears in the output and can be tracked.
Summary
@pytest.mark.skip(reason="...") unconditionally skips a test
@pytest.mark.skipif(condition, reason="...") skips based on runtime conditions
pytest.importorskip("module") skips if an optional dependency is missing
@pytest.mark.xfail(reason="...") lets the test run but expects it to fail
pytest.skip("reason") skips at runtime from inside a test body or fixture
Use custom markers with pytest -m "not slow" to exclude test categories from the command line