Python unittestのskipとは?テストを条件付きで飛ばす方法

unittestには、条件によってテストを実行しないための仕組みが用意されています。

環境依存のテストや、未修正のバグに対するテストを失敗扱いにせず記録として残せます。


4つのデコレータ

import unittest
import sys

class T(unittest.TestCase):
    @unittest.skip("理由を書く")
    def test_skip(self):
        self.fail("実行されない")

    @unittest.skipIf(sys.platform == "darwin", "macOSでは不要")
    def test_skipif(self):
        self.fail("macでは実行されない")

    @unittest.skipUnless(sys.platform == "win32", "Windows専用")
    def test_skipunless(self):
        self.fail("実行されない")

    @unittest.expectedFailure
    def test_expected(self):
        self.fail("既知のバグ")

if __name__ == "__main__":
    unittest.main(verbosity=2)

実行結果(macOS):

test_expected (__main__.T) ... expected failure
test_skip (__main__.T) ... skipped '理由を書く'
test_skipif (__main__.T) ... skipped 'macOSでは不要'
test_skipunless (__main__.T) ... skipped 'Windows専用'

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK (skipped=3, expected failures=1)

最後がOKです。スキップも既知の失敗も、テスト全体の結果は成功のままになります。

デコレータ 動き
@skip(理由) 常に飛ばす
@skipIf(条件, 理由) 条件が真なら飛ばす
@skipUnless(条件, 理由) 条件が偽なら飛ばす
@expectedFailure 失敗を成功として扱う

理由は必須

@unittest.skip("外部APIが必要なため、CIでは実行しない")

理由を書かないと、あとから誰も判断できません。

スキップしたテストは実質的に存在しないのと同じです。理由が書いてあれば、「この条件が解消したら戻せる」と分かります。

理由のないスキップは、そのまま忘れ去られて永久にスキップされ続けます。


実行時に判断する

    def test_runtime_skip(self):
        if 外部サービスが使えない():
            self.skipTest("APIキーが設定されていません")
        ...

実行結果:

test_runtime_skip (__main__.T) ... skipped 'APIキーが設定されていません'

デコレータでは判断できない条件に使います。

setUp()の中でも呼べます。その場合、そのクラスの各テストが個別にスキップされます。

    def setUp(self):
        if not os.environ.get("API_KEY"):
            self.skipTest("APIキーが未設定")

expectedFailureの使いどころ

    @unittest.expectedFailure
    def test_known_bug(self):
        self.assertEqual(buggy_function(), "正しい値")

「バグがあることは分かっているが、まだ直していない」状態を記録できます。

スキップとの違いは決定的です。

@skip @expectedFailure
テストの実行 されない される
直ったとき 気づけない 通知される

直ったときの挙動を見てみます。

    @unittest.expectedFailure
    def test_unexpected_pass(self):
        pass                      # 成功してしまう

実行結果:

test_unexpected_pass (__main__.T) ... unexpected success

FAILED (unexpected successes=1)

「予期しない成功」として報告されます。

バグが直ったことに気づけるので、デコレータを外すという対応が取れます。スキップではこうはいきません。


クラスごとスキップする

@unittest.skipUnless(sys.platform == "win32", "Windows専用のテスト")
class TestWindows(unittest.TestCase):
    def test_a(self): ...
    def test_b(self): ...

クラスに付ければ全メソッドがスキップされます。

OS依存・DB必須など、まとまった単位でスキップしたいときに便利です。


使いすぎない

スキップは便利ですが、多用するとテストの意味が失われます。

  • CIで常に3割スキップ → その3割は動作が保証されていない
  • 理由が古い → 条件が解消しても誰も戻さない
  • 落ちるから飛ばした → バグを隠しているだけ

判断の目安は次のとおりです。

状況 対応
環境に依存する(OS・外部API) skipIf
既知のバグで未修正 expectedFailure
外部依存が理由で落ちる モックで差し替える
原因が分からず落ちる スキップせず調査する

3つ目が重要です。外部APIに依存しているなら、スキップせずモックにすれば実行できます。

「Python mock.patch()とは?外部依存を差し替えてテストする方法」を参照してください。


skipのまとめ

  • @skip・@skipIf・@skipUnlessの3つ
  • 理由は必ず書く
  • 実行時の判断はself.skipTest()
  • @expectedFailureは実行したうえで失敗を許容する
  • 直るとunexpected successで気づける
  • 外部依存が理由ならモックを検討する

あわせて読みたい

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら