Пропуск тестов и группировка

Как пропускать тесты, помечать известные баги и группировать тесты в наборы.

skip — пометка «не выполнять этот тест сейчас». expectedFailure — «этот тест должен падать (известный баг), и это не ошибка сборки».

Пропуск тестов: skip

Иногда тест нельзя или не нужно запускать: фича ещё не готова, тест зависит от платформы, нужна версия библиотеки. Вместо удаления или комментирования его пропускают — тогда он остаётся в отчёте как «skipped», а не теряется.

  • @unittest.skip("причина") — всегда пропустить.
  • @unittest.skipIf(условие, "причина") — пропустить, если условие истинно.
  • @unittest.skipUnless(условие, "причина") — пропустить, если условие ложно.

expectedFailure: известный, но ещё не исправленный баг

Если вы знаете о баге, но пока не можете его починить, тест на него будет падать и «портить» зелёную сборку. Декоратор @unittest.expectedFailure говорит: «я знаю, что тут падает». Тест считается ожидаемо упавшим — сборка остаётся зелёной. А если он вдруг пройдёт, unittest пометит это как unexpected success — сигнал «баг починили, убери пометку».

Запускаемый пример

import unittest

class TestStatuses(unittest.TestCase):
    def test_normal(self):
        self.assertEqual(2 + 2, 4)

    @unittest.skip("ещё не реализовано")
    def test_future_feature(self):
        self.fail("этот код не выполнится")

    @unittest.skipIf(1 > 0, "пропускаем по условию")
    def test_conditional(self):
        self.fail("и этот тоже пропущен")

    @unittest.expectedFailure
    def test_known_bug(self):
        # Известный баг: пока ждём 2, но получаем 3
        self.assertEqual(1 + 2, 2)

unittest.main(argv=[''], exit=False, verbosity=2)

Вывод: (важна итоговая строка со сводкой статусов)

test_conditional ... skipped 'пропускаем по условию'
test_future_feature ... skipped 'ещё не реализовано'
test_known_bug ... expected failure
test_normal ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK (skipped=2, expected failures=1)

Сборка зелёная (OK), хотя один тест помечен как «ожидаемо падающий», а два пропущены. Всё это видно в отчёте — ничего не потерялось.

Группировка тестов: TestSuite

Обычно unittest сам собирает тесты. Но иногда нужно вручную составить набор (suite) из конкретных тестов — например, «быстрый прогон» только важных проверок. Для этого есть TestSuite:

import unittest

class TestMath(unittest.TestCase):
    def test_add(self):
        self.assertEqual(1 + 1, 2)
    def test_sub(self):
        self.assertEqual(5 - 2, 3)
    def test_mul(self):
        self.assertEqual(2 * 3, 6)

# Собираем набор вручную: только два теста из трёх
suite = unittest.TestSuite()
suite.addTest(TestMath("test_add"))
suite.addTest(TestMath("test_mul"))

result = unittest.TextTestRunner(verbosity=2).run(suite)
print("Тестов в наборе:", result.testsRun)

Вывод: (в набор попали только два выбранных теста)

Ran 2 tests in 0.000s

OK
Тестов в наборе: 2

На практике ручные наборы нужны редко — чаще полагаются на автообнаружение. Но знать механизм полезно: на нём строятся свои стратегии запуска (smoke-наборы, регрессионные прогоны).

Когда уместно пропускать, а когда — нет

Пропуск — инструмент честный, но опасный при злоупотреблении. Хороший повод для skip: тест зависит от условия, которого сейчас нет (нужная ОС, версия библиотеки, внешний сервис в интеграционных тестах). Плохой повод — «тест мешает, временно отключу». Такой пропуск с пометкой «временно» живёт годами, и поведение остаётся непроверенным. Если тест больше не нужен — удалите его осознанно; если нужен, но падает — чините, а не прячьте. skip всегда требует причины именно для того, чтобы вы её обдумали.

expectedFailure против skip

Эти два механизма решают разные задачи, и их легко перепутать. skip говорит «не запускай этот тест вовсе». expectedFailure — «запусти, я знаю, что он упадёт». Разница принципиальная: ожидаемо падающий тест выполняется, и если баг внезапно починят, unittest сообщит об «unexpected success». Это автоматическое напоминание убрать пометку. Пропущенный же тест молчит и ничего не отслеживает. Поэтому для известного, но временного бага лучше expectedFailure — он не даст забыть про проблему.

Итог

  • @skip, @skipIf, @skipUnless — пропускают тесты, оставляя их в отчёте.
  • @expectedFailure — помечает известный баг; сборка остаётся зелёной.
  • Неожиданный успех ожидаемо-падающего теста — сигнал убрать пометку.
  • TestSuite позволяет собирать наборы тестов вручную.
Проверьте себя
1. Что делает декоратор @unittest.skip("причина")?
AУдаляет тест
BПропускает тест, оставляя его в отчёте как skipped
CЗапускает тест дважды
DПомечает тест как упавший
2. Зачем нужен @unittest.expectedFailure?
AЧтобы тест всегда падал
BЧтобы пометить известный баг: падение ожидаемо и не делает сборку красной
CЧтобы ускорить тест
DЧтобы пропустить тест по условию
3. Для чего служит TestSuite?
AДля измерения покрытия
BДля ручной сборки набора из конкретных тестов
CДля замены assert-методов
DДля установки pytest
Поддержать проект