Модуль 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 | тест прошёл, ожидание выполнено |
FAIL | assert не выполнился: код работает, но не так, как ждали |
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 — упавший код.