Как использовать тесты на основе unittest с pytest
pytest поддерживает запуск тестов на основе Python unittest из коробки. Это позволяет использовать существующие наборы тестов на основе unittest с pytest в качестве средства запуска тестов, а также постепенно адаптировать набор тестов, чтобы в полной мере воспользоваться возможностями pytest.
Чтобы запустить существующий набор тестов в стиле unittest с помощью pytest, введите:
pytest tests
pytest автоматически соберёт подклассы unittest.TestCase и их методы test в файлах test_*.py или *_test.py.
Поддерживаются почти все возможности unittest:
-
unittest.skip()/unittest.skipIf()декораторы -
unittest.TestCase.setUp()/unittest.TestCase.tearDown() -
unittest.TestCase.setUpClass()/unittest.TestCase.tearDownClass() -
unittest.setUpModule()/unittest.tearDownModule() -
unittest.TestCase.subTest()(начиная с версии9.0)
На данный момент pytest не поддерживает следующие возможности:
Преимущества из коробки
Запуская набор тестов с помощью pytest, вы можете воспользоваться рядом возможностей, в большинстве случаев не изменяя существующий код:
- Получить более информативные трассировки стека;
- перехват stdout и stderr;
-
параметры выбора тестов с помощью флагов
-kи-m; - остановка после первой (или N) ошибки;
- –pdb — параметр командной строки для отладки при сбоях тестов (см. примечание ниже);
- Распределение тестов по нескольким процессорам с помощью плагина pytest-xdist;
- Использование обычных инструкций assert вместо функций
self.assert*(в этом очень помогает unittest2pytest);
Возможности pytest в подклассах unittest.TestCase
В подклассах unittest.TestCase работают следующие возможности pytest:
Следующие возможности pytest не работают и, вероятно, никогда не будут работать из-за различий в философии проектирования:
-
Фикстуры (за исключением фикстур
autouse, см. ниже); - Параметризация;
- Пользовательские хуки;
Сторонние плагины могут работать или не работать должным образом — это зависит от плагина и набора тестов.
Добавление фикстур pytest в подклассы unittest.TestCase с помощью меток
Запуск тестов unittest с помощью pytest позволяет использовать механизм фикстур pytest в тестах в стиле unittest.TestCase. Предположим, вы хотя бы бегло ознакомились с возможностями фикстур pytest. Рассмотрим пример, в котором используется фикстура pytest db_class для создания кэшируемого на уровне класса объекта базы данных, на который затем ссылается тест в стиле unittest:
# content of conftest.py
# we define a fixture function below and it will be "used" by
# referencing its name from tests
import pytest
@pytest.fixture(scope="class")
def db_class(request):
class DummyDB:
pass
# set a class attribute on the invoking test context
request.cls.db = DummyDB()
Здесь определяется функция фикстуры db_class, которая при использовании вызывается один раз для каждого класса тестов и устанавливает атрибут db на уровне класса, присваивая ему экземпляр DummyDB. Функция фикстуры получает специальный объект request, предоставляющий доступ к контексту запрашивающего теста, например к атрибуту cls, указывающему на класс, в котором используется фикстура. Такая архитектура отделяет написание фикстур от кода самих тестов и позволяет повторно использовать фикстуру, просто сославшись на неё по имени. Теперь напишем класс unittest.TestCase, используя определённую нами фикстуру:
# content of test_unittest_db.py
import unittest
import pytest
@pytest.mark.usefixtures("db_class")
class MyTest(unittest.TestCase):
def test_method1(self):
assert hasattr(self, "db")
assert 0, self.db # fail for demo purposes
def test_method2(self):
assert 0, self.db # fail for demo purposes
Декоратор класса @pytest.mark.usefixtures("db_class") гарантирует, что функция фикстуры pytest db_class будет вызвана один раз для каждого класса. Благодаря намеренно завершающимся ошибкой инструкциям assert мы можем посмотреть значения self.db в трассировке стека:
$ pytest test_unittest_db.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-9.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
test_unittest_db.py FF [100%]
================================= FAILURES =================================
___________________________ MyTest.test_method1 ____________________________
self = <test_unittest_db.MyTest testMethod=test_method1>
def test_method1(self):
assert hasattr(self, "db")
> assert 0, self.db # fail for demo purposes
^^^^^^^^^^^^^^^^^
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
E assert 0
test_unittest_db.py:11: AssertionError
___________________________ MyTest.test_method2 ____________________________
self = <test_unittest_db.MyTest testMethod=test_method2>
def test_method2(self):
> assert 0, self.db # fail for demo purposes
^^^^^^^^^^^^^^^^^
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
E assert 0
test_unittest_db.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: <conft...
FAILED test_unittest_db.py::MyTest::test_method2 - AssertionError: <conft...
============================ 2 failed in 0.12s =============================
Эта стандартная трассировка стека pytest показывает, что оба метода тестирования используют один и тот же экземпляр self.db, как и предполагалось при написании функции фикстуры с областью видимости класса.
Использование автоматически применяемых фикстур и доступ к другим фикстурам
Хотя обычно лучше явно указывать фикстуры, необходимые для конкретного теста, иногда может понадобиться, чтобы фикстуры автоматически применялись в определённом контексте. В конце концов, традиционный стиль настройки unittest предполагает неявное использование фикстур, и, возможно, вы к этому привыкли или предпочитаете такой подход.
Вы можете пометить функции фикстур флагом @pytest.fixture(autouse=True) и определить функцию фикстуры в контексте, где она должна использоваться. Рассмотрим фикстуру initdir, которая запускает все методы тестирования класса TestCase во временном каталоге с предварительно инициализированным samplefile.ini. Сама фикстура initdir использует встроенную фикстуру pytest tmp_path, чтобы создать отдельный временный каталог для каждого теста:
# content of test_unittest_cleandir.py
import unittest
import pytest
class MyTest(unittest.TestCase):
@pytest.fixture(autouse=True)
def initdir(self, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path) # change to pytest-provided temporary directory
tmp_path.joinpath("samplefile.ini").write_text("# testdata", encoding="utf-8")
def test_method(self):
with open("samplefile.ini", encoding="utf-8") as f:
s = f.read()
assert "testdata" in s
Благодаря флагу autouse функция фикстуры initdir будет использоваться для всех методов класса, в котором она определена. Это сокращённый вариант применения маркера @pytest.mark.usefixtures("initdir") к классу, как в предыдущем примере.
Запуск этого модуля тестов …:
$ pytest -q test_unittest_cleandir.py . [100%] 1 passed in 0.12s
… даёт один пройденный тест, поскольку функция фикстуры initdir была выполнена до test_method.
Примечание
Методы unittest.TestCase не могут напрямую получать аргументы фикстур, поскольку такая возможность, вероятно, помешала бы запуску обычных наборов тестов unittest.TestCase.
Примеры выше с usefixtures и autouse помогут добавить фикстуры pytest в наборы тестов unittest.
Вы также можете постепенно отказаться от наследования от unittest.TestCase, перейти к обычным инструкциям assert и затем поэтапно начать пользоваться полным набором возможностей pytest.
Примечание
Из-за архитектурных различий между двумя фреймворками настройка и очистка для тестов на основе unittest выполняются на этапе call тестирования, а не на стандартных этапах pytest и setup фреймворка teardown. В некоторых ситуациях это важно понимать, особенно при анализе ошибок. Например, если при настройке набора тестов на основе unittest возникают ошибки, pytest не сообщит об ошибках на этапе setup, а вместо этого выдаст ошибку на этапе call.
© 2015–2026 Holger Krekel and pytest-dev team
Licensed under the MIT License.
https://docs.pytest.org/en/stable/how-to/unittest.html