Back to Blog
Python

Python pytest marks: skip, skipif, and xfail

python pytest marks skip skipif and xfail: Learn how to use pytest's skip, skipif, and xfail markers to control test execution, handle platform-specific dependencies,...

pytesttest skippingxfailconditional teststest markers
A visual representation of pytest markers controlling test execution with skip, skipif, and xfail conditions.

python pytest marks skip skipif and xfail requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When a test depends on an optional dependency, a platform-specific feature, or a known bug that hasn't been fixed yet, you need a way to exclude it from the passing set without deleting it. Pytest provides three markers—skip, skipif, and xfail—to handle these situations. Each marker serves a distinct purpose, and using them correctly keeps your test suite honest about what is actually working.

Why Use Pytest Marks for Skipping and Expected Failures

A test suite that randomly fails because a network service is down or because the code is running on Windows instead of Linux is not useful. The standard approach is to skip such tests conditionally. Pytest's markers let you encode the reason directly in the test, so the skip is visible in the report and does not silently hide a regression.

The skip marker unconditionally skips a test. The skipif marker skips a test only when a condition is true. The xfail marker marks a test that is expected to fail, which is different from skipping: the test still runs, but a failure is reported as expected rather than as a regression. Understanding when to use each is the core of managing tests that cannot pass in the current environment.

The skip Marker: Skipping Tests Unconditionally

Use @pytest.mark.skip when a test should never run in the current codebase. A common reason is that the test exercises a feature that has been temporarily disabled or requires a resource that is never available in the CI environment.

import pytest @pytest.mark.skip(reason="Feature not implemented yet") def test_new_feature(): assert False

The reason argument is optional but strongly recommended. It appears in the test report, so anyone reading the output knows exactly why the test was skipped. Without a reason, the report shows a generic skip, which is less informative.

Skipping is not the same as commenting out a test. A skipped test is still collected and reported, so you can see that it exists and why it is not running. This makes it easier to track work that still needs to be done.

The skipif Marker: Conditional Skipping

@pytest.mark.skipif is the most flexible marker. It takes a condition and a reason. When the condition evaluates to True, the test is skipped. This is ideal for platform-specific tests, optional dependencies, or environment variables.

import sys import pytest @pytest.mark.skipif(sys.platform == "win32", reason="Requires POSIX file permissions") def test_file_permissions(): # Unix-only permission check pass

The condition can be any Python expression that evaluates to a boolean. It is evaluated at collection time, not at test execution time. This means you can reference imported modules, environment variables, or even custom functions, as long as they are available when the test module is imported.

A common pattern is to check whether an optional dependency is installed:

import importlib.util import pytest has_numpy = importlib.util.find_spec("numpy") is not None @pytest.mark.skipif(not has_numpy, reason="numpy is not installed") def test_numpy_operation(): import numpy as np assert np.array([1, 2]).sum() == 3

This keeps the test in the source but prevents a collection error when the dependency is missing. The test will appear as skipped in the report, which is more informative than a module-level try/except that silently omits the test.

The xfail Marker: Marking Expected Failures

@pytest.mark.xfail is for tests that are expected to fail. The test runs, but if it fails, the failure is reported as expected. If it unexpectedly passes, the test is reported as xpass. This is useful when you have a known bug that you intend to fix later, or when you are developing a feature and want to document that the current behavior is not yet correct.

import pytest @pytest.mark.xfail(reason="Known bug in parser") def test_parser_edge_case(): assert parse("invalid") is None

By default, an xfail test that fails does not fail the suite. The report shows it as xfailed. If the test passes, it is shown as xpassed, which is a signal that the expected failure no longer exists and the marker should be removed.

You can control this behavior with the strict parameter. When strict=True, an xpass is treated as a failure. This is valuable in continuous integration: if a bug is fixed, you want the suite to fail so you remember to remove the marker and add a proper assertion.

@pytest.mark.xfail(reason="Bug #1234", strict=True) def test_known_bug(): assert current_behavior() == expected_behavior()

When strict=True, the test is expected to fail. If it passes, the suite fails, alerting you that the bug is fixed and the marker is stale.

Combining Marks and Using strict Parameters

Markers can be stacked. A test can be both skipif and xfail, though the semantics can become confusing. In practice, it is better to keep them separate. If a test is skipped, it never runs, so xfail has no effect. If a test is marked xfail, it runs and is expected to fail. Combining them rarely makes sense.

The strict parameter also exists on skipif? No, skipif does not have a strict parameter. strict is specific to xfail. For skipif, the condition is evaluated once; there is no concept of unexpected pass because the test never runs.

You can also use the reason parameter on all three markers. It is the primary way to communicate intent to other developers and to yourself when you revisit the test later.

Runtime Behavior and Reporting

When a test is skipped, pytest does not execute its body. The test is reported as SKIPPED in the summary line. When a test is marked xfail and fails, it is reported as XFAIL; if it passes, it is reported as XPASS. These statuses appear in the terminal output and in JUnit XML reports, so CI systems can distinguish between a real failure, a skip, and an expected failure.

This distinction matters for alerting. A skipped test should not trigger a failure. An xfail test that fails should not trigger a failure either, but an xpass with strict=True should. By configuring your CI to treat xpass as a failure, you ensure that stale markers are not silently ignored.

Another runtime consideration is that skipif conditions are evaluated at import time. If the condition depends on a value that changes during the test session, the skip decision is made before any test runs. This is usually what you want, but it means you cannot use a condition that depends on a fixture or on the state of a previous test.

Common Pitfalls and Maintainability Considerations

One common mistake is using skip when you should use xfail. If a test is failing because of a bug, skip hides the bug entirely. xfail documents that the test is expected to fail, so when the bug is fixed, the test will show as xpass and you can remove the marker. Using skip for known bugs makes it easy to forget that the bug exists.

Another mistake is putting a skipif condition that is too broad. For example, skipping a test on all Windows versions when only one specific version has a problem. This reduces coverage on other Windows environments. Use precise conditions that target the exact environment or dependency version.

Maintainability also suffers when reason strings are vague. Write a reason that explains the underlying cause, such as a bug tracker ID or a specific dependency version. This makes it possible to audit the test suite later and decide whether the marker is still necessary.

Finally, be careful with xfail on tests that are not deterministic. If a test sometimes passes and sometimes fails, marking it xfail will produce a mix of xpass and xfail runs. This is a sign that the test itself is flaky, not that the feature is incomplete. The marker should not be used to hide flakiness; it should be reserved for tests that fail consistently for a known reason.

When you introduce a new marker, check the pytest documentation for the version you are using. The behavior of strict and the reporting format have evolved across pytest versions. The examples here use the standard syntax that has been stable since pytest 3.0, but always verify against your installed version to avoid surprises.

python pytest marks skip skipif and xfail: Practical Usage a | RYUSLOG DEV