Фикстуры: setUp и tearDown
Как убрать дублирование подготовки данных с помощью фикстур setUp и tearDown.
Фикстура — общая подготовка (и уборка) окружения для теста: данные, объекты, временные файлы, соединения.
Проблема: повторяющаяся подготовка
Часто всем тестам в классе нужен один и тот же стартовый объект. Создавать его в каждом тесте — копипаста. Метод setUp вызывается перед каждым тестом автоматически, а tearDown — после каждого, даже если тест упал.
setUp(self)— подготовка: выполняется до каждогоtest_*.tearDown(self)— уборка: выполняется после каждогоtest_*.
Порядок вызовов наглядно
Добавим print, чтобы увидеть, когда что вызывается. Для двух тестов цикл «setUp → тест → tearDown» повторится дважды:
import unittest
class TestOrder(unittest.TestCase):
def setUp(self):
self.data = [1, 2, 3]
print("setUp: подготовили данные")
def tearDown(self):
print("tearDown: убрали за собой")
def test_len(self):
print(" -> test_len")
self.assertEqual(len(self.data), 3)
def test_sum(self):
print(" -> test_sum")
self.assertEqual(sum(self.data), 6)
unittest.main(argv=[''], exit=False)
Вывод: (строки print идут в stdout; видно, что фикстуры обрамляют каждый тест)
setUp: подготовили данные -> test_len tearDown: убрали за собой setUp: подготовили данные -> test_sum tearDown: убрали за собой
Главное: self.data создаётся заново перед каждым тестом. Поэтому тесты изолированы — изменения в одном не утекают в другой.
Практический пример: общий объект
Тестируем класс «корзина». В setUp создаём пустую корзину, и каждый тест начинает с чистого состояния:
import unittest
class Cart:
def __init__(self):
self.items = []
def add(self, name):
self.items.append(name)
def total(self):
return len(self.items)
class TestCart(unittest.TestCase):
def setUp(self):
self.cart = Cart() # свежая корзина перед каждым тестом
def test_empty(self):
self.assertEqual(self.cart.total(), 0)
def test_add_one(self):
self.cart.add("книга")
self.assertEqual(self.cart.total(), 1)
def test_add_two(self):
self.cart.add("книга")
self.cart.add("ручка")
self.assertEqual(self.cart.total(), 2)
unittest.main(argv=[''], exit=False, verbosity=2)
Вывод:
test_add_one (__main__.TestCart.test_add_one) ... ok test_add_two (__main__.TestCart.test_add_two) ... ok test_empty (__main__.TestCart.test_empty) ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.000s OK
Без setUp пришлось бы писать self.cart = Cart() в начале каждого теста. Фикстура убирает это дублирование.
Зачем нужен tearDown
tearDown освобождает ресурсы: закрывает файлы, удаляет временные данные, откатывает изменения. Он выполняется даже если тест упал — поэтому ресурс не «зависнет». Типичные задачи уборки: удалить временный файл, закрыть соединение, сбросить глобальное состояние.
Важно понимать порядок гарантий. Если setUp успел создать ресурс, а сам тест упал на assert — tearDown всё равно вызовется и приберёт за тестом. Это и есть причина выносить уборку именно в tearDown, а не писать её в конце каждого теста: код в конце теста не выполнится, если тест упал раньше, и ресурс утечёт. А вот если ошибка возникнет в самом setUp, тест будет помечен как ERROR и не запустится вовсе — это сигнал, что сломалась подготовка, а не проверяемая логика.
Когда фикстуры не нужны
Не превращайте setUp в свалку. Если данные нужны только одному тесту — создавайте их прямо в нём, это понятнее. Фикстура оправдана, когда одна и та же подготовка реально повторяется в нескольких тестах. Иначе чтение усложняется: чтобы понять тест, приходится прыгать глазами между ним и setUp.
Итог
setUpзапускается перед каждым тестом,tearDown— после (даже при падении).- Фикстуры убирают дублирование подготовки и обеспечивают изоляцию тестов.
- Каждый тест стартует с чистого состояния — это делает их независимыми.
tearDownосвобождает ресурсы: файлы, соединения, временные данные.