Skipping Tests and Expected Failures
Skipping tests
Section titled “Skipping tests”Skip a test when:
- a dependency isn’t available
- a feature is not supported on the OS
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)Expected failures
Section titled “Expected failures”Use when:
- you want to keep a failing test visible
- you’re tracking known issues
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.
🧪 Try It Yourself
Section titled “🧪 Try It Yourself”Exercise 1 – Write a unittest TestCase
Section titled “Exercise 1 – Write a unittest TestCase”Exercise 2 – assertRaises
Section titled “Exercise 2 – assertRaises”Exercise 3 – setUp and tearDown
Section titled “Exercise 3 – setUp and tearDown”Skips are recorded, not hidden
Section titled “Skips are recorded, not hidden”@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:
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 failures are a different thing
Section titled “Expected failures are a different thing”@unittest.expectedFailure
def test_known_bug(self):
self.assertEqual(add(1, 1), 3) # known wrong, tracked in issue #123 flowchart TD
E["@expectedFailure"] --> R{"does the test fail?"}
R -->|"yes"| X["'expected failure' - the SUITE STILL PASSES"]
R -->|"no"| U["'unexpected success' - the SUITE FAILS"]
S["@skip"] --> N["never runs at all"]
The second branch is the interesting one. Measured on a suite of eight tests where one
@expectedFailure test actually passed:
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.
Choosing between them
Section titled “Choosing between them”| situation | use |
|---|---|
| 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 it | neither — 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.
Skipping mid-test
Section titled “Skipping mid-test”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.
See it move
Section titled “See it move”Check yourself
Section titled “Check yourself”-
A test decorated @expectedFailure unexpectedly passes. What happens to the suite?
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.
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.
-
What is the practical risk of @skipUnless(HAS_DB, 'needs a database')?
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.
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.
-
When should you use @skip rather than @expectedFailure?
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.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.coffeeCtapch.feedbackHeading
pch.feedbackSubheading