Модуль unittest: первый TestCase

Знакомимся с модулем unittest и пишем первый настоящий тест-класс.

unittest — встроенный в Python модуль для тестирования. Его не нужно устанавливать: он есть в стандартной библиотеке любой версии Python.

Из чего состоит тест на unittest

Тесты в unittest оформляются как классы, унаследованные от unittest.TestCase. Внутри — методы, имена которых начинаются с test_. Каждый такой метод — отдельный тест. Внутри метода вызывают assert-методы (например, self.assertEqual(...)), которые проверяют ожидания.

  • class TestX(unittest.TestCase) — контейнер для группы тестов.
  • def test_something(self) — один тест. Префикс test_ обязателен: по нему unittest находит тесты.
  • self.assertEqual(a, b) — проверка «a равно b». Если нет — тест падает.

Первый запускаемый пример

Тестируем простую функцию. Чтобы результат был виден в браузере, мы запускаем тесты программно через unittest.main(argv=[''], exit=False, verbosity=2). Параметр argv=[''] убирает разбор аргументов командной строки, exit=False не даёт завершить процесс, verbosity=2 печатает каждый тест отдельной строкой.

import unittest

def add(a, b):
    return a + b

class TestAdd(unittest.TestCase):
    def test_positive(self):
        self.assertEqual(add(2, 3), 5)

    def test_negative(self):
        self.assertEqual(add(-1, -1), -2)

    def test_zero(self):
        self.assertEqual(add(0, 5), 5)

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

Вывод: (unittest печатает отчёт; точное оформление имени теста зависит от версии Python, но устойчивая концовка такая)

test_negative (__main__.TestAdd.test_negative) ... ok
test_positive (__main__.TestAdd.test_positive) ... ok
test_zero (__main__.TestAdd.test_zero) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

Три теста, все прошли — OK. Обратите внимание: тесты выполняются в алфавитном порядке имён, а не в порядке записи в файле. Поэтому тесты не должны зависеть друг от друга.

Что происходит, когда тест падает

Если ожидание не совпадает с реальностью, unittest показывает, какой тест упал и почему. Сломаем ожидание нарочно:

import unittest

def add(a, b):
    return a + b

class TestAdd(unittest.TestCase):
    def test_ok(self):
        self.assertEqual(add(2, 2), 4)

    def test_broken(self):
        # Намеренно неверное ожидание
        self.assertEqual(add(2, 2), 5)

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

Вывод: (устойчивая часть)

... FAIL
... ok

======================================================================
FAIL: test_broken (...)
AssertionError: 4 != 5

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

Сообщение 4 != 5 сразу говорит: функция вернула 4, а мы ждали 5. Хорошие assert-методы дают понятную диагностику без лишних усилий с вашей стороны.

FAIL и ERROR — это разное

ИсходЧто значит
okтест прошёл, ожидание выполнено
FAILassert не выполнился: код работает, но не так, как ждали
ERRORв коде вылетело необработанное исключение (например, опечатка, TypeError)

FAIL — это «логика неверна», ERROR — «код вообще упал». Различать полезно: ERROR часто означает баг в самом тесте или в коде, а не неверное ожидание.

Почему именно класс и наследование

Может показаться лишним заворачивать тесты в класс. Но наследование от TestCase — это то, что даёт вам всю инфраструктуру: десятки assert-методов, фикстуры setUp/tearDown, корректный подсчёт и изоляцию тестов. Сам класс работает как контейнер: он группирует логически связанные тесты (например, все тесты одной функции) под одним именем. В большом проекте таких классов много, и каждый отвечает за свою область — это помогает ориентироваться в сотнях тестов.

Что считается одним тестом

Каждый метод с префиксом test_ — это один независимый тест со своим вердиктом в отчёте. unittest вызывает их по очереди, и падение одного не мешает выполниться остальным: даже если test_positive упал, test_zero всё равно отработает и покажет свой статус. Именно поэтому важно не складывать всё в один метод — иначе первый же провалившийся assert прервёт метод, и про остальные проверки внутри него вы ничего не узнаете. Дробление на отдельные test_* даёт точную картину: видно ровно, что прошло, а что нет.

Итог

  • Тесты — это классы-наследники unittest.TestCase с методами test_*.
  • self.assertEqual(a, b) и другие assert-методы проверяют ожидания.
  • Для видимого вывода в браузере запускаем unittest.main(argv=[''], exit=False, verbosity=2).
  • Тесты идут в алфавитном порядке и не должны зависеть друг от друга.
  • FAIL — неверное ожидание, ERROR — упавший код.
Проверьте себя
1. Какой префикс обязателен в имени метода-теста в unittest?
Acheck_
Btest_
Cassert_
Drun_
2. Зачем при запуске в браузере используют unittest.main(argv=[''], exit=False)?
AЧтобы тесты выполнялись параллельно
BЧтобы не разбирать аргументы командной строки и не завершать процесс
CЧтобы отключить assert-методы
DЧтобы скрыть вывод
3. Чем FAIL отличается от ERROR в отчёте unittest?
AЭто одно и то же
BFAIL — не выполнился assert; ERROR — в коде вылетело необработанное исключение
CFAIL — синтаксическая ошибка; ERROR — логическая
DERROR бывает только в setUp
Поддержать проект