Skip to content

Skipping Tests and Expected Failures

Skip a test when:

  • a dependency isn’t available
  • a feature is not supported on the OS
skip_examples.py
import sys
import unittest
 
 
class TestPlatform(unittest.TestCase):
    @unittest.skipIf(sys.platform.startswith("win"), "Not supported on Windows")
    def test_linux_only_behavior(self):
        self.assertTrue(True)
 
    @unittest.skip("Temporarily disabled")
    def test_disabled(self):
        self.assertTrue(False)

Use when:

  • you want to keep a failing test visible
  • you’re tracking known issues
expected_failure.py
import unittest
 
 
class TestKnownBug(unittest.TestCase):
    @unittest.expectedFailure
    def test_buggy_behavior(self):
        self.assertEqual(1, 2)

Don’t overuse skips—treat them as temporary.

skips.py
@unittest.skip("demonstrating skip")
def test_skipped(self):
    self.fail("never runs")
 
@unittest.skipIf(sys.platform == "win32", "POSIX only")
def test_skipif(self):
    ...
 
@unittest.skipUnless(HAS_DB, "needs a database")
def test_needs_db(self):
    ...

Measured output:

unittest discover -v
test_skipif  ... skipped 'conditionally skipped'
test_skipped ... skipped 'demonstrating skip'

The reason string is printed, which is the whole point — a skip with a good reason is documentation; a commented-out test is a mystery.

expected.py
@unittest.expectedFailure
def test_known_bug(self):
    self.assertEqual(add(1, 1), 3)      # known wrong, tracked in issue #123
diagram Diagram mermaid

The second branch is the interesting one. Measured on a suite of eight tests where one @expectedFailure test actually passed:

result
Ran 8 tests in 0.004s
 
FAILED (skipped=2, expected failures=1, unexpected successes=1)

The run failed — with no ordinary failure anywhere in it. An unexpected success means the bug was fixed and the marker is now lying, so the framework makes you go and remove it. That is the mechanism that stops @expectedFailure becoming permanent.

situationuse
the feature is not available on this platform@skipIf / @skipUnless
a dependency is missing in this environment@skipUnless
a known bug, with an issue open@expectedFailure
the test is flaky and nobody has looked at itneither — fix or delete it

The last row matters. A skip added to make CI green is a test that no longer exists, and it will be quietly carried for years.

runtime_skip.py
def test_optional_feature(self):
    if not shutil.which("ffmpeg"):
        self.skipTest("ffmpeg not installed")
    ...

self.skipTest() works after the test has started, which is what you need when the condition can only be determined at runtime.

sketch skip, expected failure, and unexpected success p5.js
A skip never runs. An expected failure passes the suite when it fails, and FAILS the suite when it unexpectedly passes.
pch.quizTag pch.quizDefaultTitle
  1. A test decorated @expectedFailure unexpectedly passes. What happens to the suite?

    pch.quizShowAnswer

    B — the suite FAILS, reported as an unexpected success — Measured: FAILED (skipped=2, expected failures=1, unexpected successes=1) with no ordinary failure present. It means the bug was fixed and the marker now lies.

  2. What is the practical risk of @skipUnless(HAS_DB, 'needs a database')?

    pch.quizShowAnswer

    B — it stops running silently the moment the environment loses its database, so watch the skip count — A suite reporting skipped=40 is not the suite you think you have. The reason string is printed, which is why a skip beats commenting a test out.

  3. When should you use @skip rather than @expectedFailure?

    pch.quizShowAnswer

    B — when the test cannot run in this environment at all, such as a platform-specific feature — expectedFailure runs the test and requires it to fail, which is how it notices the bug being fixed. skip never runs it, which is right when running is impossible.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading