Фикстуры класса: setUpClass
Фикстуры уровня класса: дорогая подготовка один раз на весь класс тестов.
setUpClass / tearDownClass — фикстуры, которые выполняются один раз на весь класс: до первого теста и после последнего.
Когда setUp слишком дорого
setUp вызывается перед каждым тестом. Если подготовка дешёвая (создать список) — отлично. Но если она дорогая — открыть соединение с БД, загрузить большой файл, поднять сервер — повторять её на каждый тест расточительно.
Тогда используют setUpClass: подготовка происходит один раз для всего класса. Это classmethod, он получает cls, а не self, и помечается декоратором @classmethod.
Пример: дорогой ресурс один раз
import unittest
class TestWithResource(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Выполнится ОДИН раз до всех тестов класса
cls.connection = "соединение открыто"
print("setUpClass: дорогая подготовка")
@classmethod
def tearDownClass(cls):
# Выполнится ОДИН раз после всех тестов класса
print("tearDownClass: ресурс освобождён")
def test_a(self):
self.assertEqual(self.connection, "соединение открыто")
def test_b(self):
self.assertTrue(self.connection)
unittest.main(argv=[''], exit=False, verbosity=2)
Вывод: (видно, что setUpClass и tearDownClass вызвались по разу, хотя тестов два)
test_a (__main__.TestWithResource.test_a) ... ok test_b (__main__.TestWithResource.test_b) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK setUpClass: дорогая подготовка tearDownClass: ресурс освобождён
Сообщения «дорогая подготовка» и «ресурс освобождён» появились по одному разу — несмотря на два теста.
Уровни фикстур: сравнение
| Фикстура | Когда | Параметр |
setUp / tearDown | вокруг каждого теста | self |
setUpClass / tearDownClass | раз на класс | cls (+@classmethod) |
Важный нюанс: общее состояние
Данные из setUpClass общие для всех тестов класса. Это плата за скорость: если один тест меняет такой объект, изменение увидят следующие тесты, и появится скрытая зависимость. Поэтому в setUpClass кладите только то, что тесты не меняют (соединения, конфиги, прогретые данные «только для чтения»). Изменяемое состояние оставляйте в setUp.
import unittest
class TestSharedCare(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Только для чтения — общий справочник
cls.rates = {"USD": 90, "EUR": 100}
def setUp(self):
# Изменяемое состояние — у каждого теста своё
self.basket = []
def test_rate(self):
self.assertEqual(self.rates["USD"], 90)
def test_basket_isolated(self):
self.basket.append("товар")
self.assertEqual(len(self.basket), 1)
unittest.main(argv=[''], exit=False, verbosity=2)
Вывод:
test_basket_isolated (__main__.TestSharedCare.test_basket_isolated) ... ok test_rate (__main__.TestSharedCare.test_rate) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK
Порядок вызова всех фикстур вместе
Когда в классе есть и фикстуры класса, и обычные, порядок такой: один раз setUpClass в начале, затем для каждого теста пара setUp → тест → tearDown, и в самом конце один раз tearDownClass. То есть фикстуры класса обрамляют весь класс снаружи, а setUp/tearDown — каждый тест внутри. Понимание этого порядка помогает решать, на каком уровне готовить тот или иной ресурс: дорогой и неизменяемый — на уровне класса, дешёвый и свежий для каждого теста — на уровне метода.
Типичная ошибка с setUpClass
Самая частая ловушка — положить в setUpClass объект, который тесты меняют, и получить «летающие» падения, зависящие от порядка тестов. Например, общий список, в который один тест добавляет элемент: следующий тест увидит «лишний» элемент и упадёт. Причём упадёт он не всегда, а в зависимости от того, какой тест выполнился раньше по алфавиту — отлаживать такое мучительно. Правило простое: в setUpClass — только то, что тесты читают, но не изменяют.
Итог
setUpClass/tearDownClassзапускаются один раз на класс — для дорогой подготовки.- Это
@classmethodс параметромcls. - В них кладут ресурсы «только для чтения»; изменяемое состояние — в
setUp. - Иначе тесты начнут скрыто влиять друг на друга через общий объект.