Как использовать тесты на основе unittest
в pytest¶
pytest
поддерживает выполнение тестов на основе Python unittest
из коробки. Он предназначен для использования существующих наборов тестов на основе unittest
для использования pytest в качестве программы запуска тестов, а также для постепенной адаптации набора тестов для использования всех возможностей pytest.
Чтобы запустить существующий набор тестов в стиле unittest
с помощью pytest
, введите:
pytest tests
pytest будет автоматически собирать unittest.TestCase
подклассы и их test
методы в test_*.py
или *_test.py
файлах.
Поддерживаются почти все функции unittest
:
Декораторы стиля
@unittest.skip
;setUp/tearDown
;setUpClass/tearDownClass
;setUpModule/tearDownModule
;
Кроме того, subtests поддерживаются плагином pytest-subtests.
На данный момент pytest не поддерживает следующие функции:
Преимущества из коробки¶
Запуская свой набор тестов с помощью pytest, вы можете использовать несколько возможностей, в большинстве случаев без необходимости изменять существующий код:
Получить more informative tracebacks;
stdout and stderr захват;
Test selection options с использованием флагов
-k
и-m
;–pdb опция командной строки для отладки при сбоях в тестировании (см. note ниже);
Распределите тесты на несколько процессоров с помощью плагина pytest-xdist;
Используйте функции plain assert-statements вместо
self.assert*
(unittest2pytest очень помогает в этом);
возможности pytest в подклассах unittest.TestCase
¶
Следующие функции pytest работают в подклассах unittest.TestCase
:
Следующие функции pytest не работают и, вероятно, никогда не будут работать из-за различных философий проектирования:
Сторонние плагины могут работать или не работать хорошо, в зависимости от плагина и набора тестов.
Смешивание фикстур pytest в подклассы unittest.TestCase
с использованием меток¶
Запуск вашего unittest с pytest
позволяет вам использовать его fixture mechanism с тестами в стиле 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
, который предоставляет доступ к the requesting test context, таким как атрибут 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 fixture db_class
вызывалась один раз для каждого класса. Благодаря намеренно неудачным утверждениям assert, мы можем посмотреть на значения self.db
в трассировке:
$ pytest test_unittest_db.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.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
, что и было нашим намерением при написании функции фикстуры, привязанной к классу, описанному выше.
Использование приспособлений autouse и доступ к другим приспособлениям¶
Хотя обычно лучше явно объявлять использование фикстур, необходимых для конкретного теста, иногда вы можете захотеть иметь фикстуры, которые автоматически используются в данном контексте. В конце концов, традиционный стиль unittest-setup предписывает использование такого неявного написания фикстур, и есть шанс, что вы привыкли к нему или он вам нравится.
Вы можете отметить функции фикстуры с помощью @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")
def test_method(self):
with open("samplefile.ini") 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
к простым утверждениям, а затем шаг за шагом начать получать преимущества от полного набора функций pytest.
Примечание
Из-за архитектурных различий между двумя фреймворками, установка и разрушение тестов на основе unittest
выполняется на этапе call
, а не на стандартных этапах pytest
setup
и teardown
. Это может быть важно понимать в некоторых ситуациях, особенно при рассуждениях об ошибках. Например, если набор, основанный на unittest
, обнаружит ошибки во время настройки, pytest
не сообщит об ошибках на этапе setup
, а вместо этого выдаст ошибку на этапе call
.