Фикстуры: 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 успел создать ресурс, а сам тест упал на asserttearDown всё равно вызовется и приберёт за тестом. Это и есть причина выносить уборку именно в tearDown, а не писать её в конце каждого теста: код в конце теста не выполнится, если тест упал раньше, и ресурс утечёт. А вот если ошибка возникнет в самом setUp, тест будет помечен как ERROR и не запустится вовсе — это сигнал, что сломалась подготовка, а не проверяемая логика.

Когда фикстуры не нужны

Не превращайте setUp в свалку. Если данные нужны только одному тесту — создавайте их прямо в нём, это понятнее. Фикстура оправдана, когда одна и та же подготовка реально повторяется в нескольких тестах. Иначе чтение усложняется: чтобы понять тест, приходится прыгать глазами между ним и setUp.

Итог

  • setUp запускается перед каждым тестом, tearDown — после (даже при падении).
  • Фикстуры убирают дублирование подготовки и обеспечивают изоляцию тестов.
  • Каждый тест стартует с чистого состояния — это делает их независимыми.
  • tearDown освобождает ресурсы: файлы, соединения, временные данные.
Проверьте себя
1. Когда вызывается метод setUp?
AОдин раз перед всеми тестами
BПеред каждым test_-методом
CТолько при ошибке
DПосле всех тестов
2. Чем полезен tearDown?
AУскоряет тесты
BОсвобождает ресурсы после каждого теста, даже если он упал
CЗапускает тесты повторно
DСчитает покрытие
3. Почему фикстуры обеспечивают изоляцию тестов?
AОни отключают другие тесты
BСостояние создаётся заново перед каждым тестом, изменения не утекают между тестами
CОни запускают тесты в случайном порядке
DОни кэшируют результаты
Поддержать проект