Начало работы
Установка pytest
Выполните следующую команду в командной строке:
pip install -U pytest
Проверьте, что установили правильную версию:
$ pytest --version
pytest 9.0.2
Создайте свой первый тест
Создайте новый файл с именем test_sample.py, содержащий функцию и тест:
# content of test_sample.py
def func(x):
return x + 1
def test_answer():
assert func(3) == 5
Тест:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-9.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
test_sample.py F [100%]
================================= FAILURES =================================
_______________________________ test_answer ________________________________
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
test_sample.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================
[100%] относится к общему прогрессу выполнения всех тестов. После завершения pytest показывает отчёт об ошибке, потому что func(3) не возвращает 5.
Note
Можно использовать инструкцию assert, чтобы проверять ожидания в тестах. Продвинутая интроспекция assert-выражений в pytest умно выводит промежуточные значения выражения assert, так что можно не запоминать множество названий унаследованных от JUnit методов.
Запуск нескольких тестов
pytest запускает все файлы вида test_*.py или *_test.py в текущем каталоге и его подкаталогах. В более общем виде он следует стандартным правилам обнаружения тестов.
Проверка, что выбрасывается определённое исключение
Используйте хелпер raises, чтобы проверить, что некоторый код выбрасывает исключение:
# content of test_sysexit.py
import pytest
def f():
raise SystemExit(1)
def test_mytest():
with pytest.raises(SystemExit):
f()
Выполните тестовую функцию в «тихом» режиме отчётов:
$ pytest -q test_sysexit.py
. [100%]
1 passed in 0.12s
Note
Флаг -q/--quiet делает вывод кратким в этом и следующих примерах.
См. Assertions about approximate equality, чтобы задать больше деталей об ожидаемом исключении.
Группировка нескольких тестов в классе
Когда тестов становится больше, может захотеться сгруппировать их в классе. pytest позволяет легко создать класс, содержащий более одного теста:
# content of test_class.py
class TestClass:
def test_one(self):
x = "this"
assert "h" in x
def test_two(self):
x = "hello"
assert hasattr(x, "check")
pytest обнаруживает все тесты согласно своим соглашениям обнаружения тестов Python, поэтому он найдёт обе функции, начинающиеся с test_. Ничего наследовать не нужно, но убедитесь, что имя класса начинается с Test, иначе класс будет пропущен. Можно просто запустить модуль, передав его имя файла:
$ pytest -q test_class.py
.F [100%]
================================= FAILURES =================================
____________________________ TestClass.test_two ____________________________
self = <test_class.TestClass object at 0xdeadbeef0001>
def test_two(self):
x = "hello"
> assert hasattr(x, "check")
E AssertionError: assert False
E + where False = hasattr('hello', 'check')
test_class.py:8: AssertionError
========================= short test summary info ==========================
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.12s
Первый тест прошёл, второй упал. Промежуточные значения в assert помогают понять причину падения.
Группировка тестов в классах может быть полезна по следующим причинам:
Организация тестов
Общие фикстуры для тестов только в этом классе
Применение меток на уровне класса с неявным применением ко всем тестам
При группировке тестов в классе стоит помнить, что каждый тест получает уникальный экземпляр класса. Если бы каждый тест делил один и тот же экземпляр класса, это было бы вредно для изоляции тестов и поощряло бы плохие практики. Это показано ниже:
# content of test_class_demo.py
class TestClassDemoInstance:
value = 0
def test_one(self):
self.value = 1
assert self.value == 1
def test_two(self):
assert self.value == 1
$ pytest -k TestClassDemoInstance -q
.F [100%]
================================= FAILURES =================================
______________________ TestClassDemoInstance.test_two ______________________
self = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002>
def test_two(self):
> assert self.value == 1
E assert 0 == 1
E + where 0 = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002>.value
test_class_demo.py:9: AssertionError
========================= short test summary info ==========================
FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0 == 1
1 failed, 1 passed in 0.12s
Обратите внимание: атрибуты, добавленные на уровне класса, являются атрибутами класса, поэтому они будут разделяться между тестами.
Сравнение чисел с плавающей точкой с pytest.approx
pytest также предоставляет набор утилит, упрощающих написание тестов. Например, можно использовать pytest.approx() для сравнения чисел с плавающей точкой, где возможны небольшие ошибки округления:
# content of test_approx.py
import pytest
def test_sum():
assert (0.1 + 0.2) == pytest.approx(0.3)
Это избавляет от ручных проверок допусков или использования math.isclose и работает со скалярами, списками и массивами NumPy.
Запрос уникального временного каталога для функциональных тестов
pytest предоставляет встроенные фикстуры/аргументы функций, чтобы запрашивать ресурсы, например уникальный временный каталог:
# content of test_tmp_path.py
def test_needsfiles(tmp_path):
print(tmp_path)
assert 0
Укажите имя tmp_path в сигнатуре тестовой функции — и pytest найдёт и вызовет фабрику фикстуры, чтобы создать ресурс перед вызовом теста. Перед запуском теста pytest создаёт уникальный (для данного вызова теста) временный каталог:
$ pytest -q test_tmp_path.py
F [100%]
================================= FAILURES =================================
_____________________________ test_needsfiles ______________________________
tmp_path = PosixPath('PYTEST_TMPDIR/test_needsfiles0')
def test_needsfiles(tmp_path):
print(tmp_path)
> assert 0
E assert 0
test_tmp_path.py:3: AssertionError
--------------------------- Captured stdout call ---------------------------
PYTEST_TMPDIR/test_needsfiles0
========================= short test summary info ==========================
FAILED test_tmp_path.py::test_needsfiles - assert 0
1 failed in 0.12s
Подробнее о временных каталогах см. в Temporary directories and files.
Посмотреть, какие встроенные pytest fixtures существуют, можно командой:
pytest --fixtures # shows builtin and custom fixtures
Обратите внимание: команда не покажет фикстуры, начинающиеся с _, если не добавить опцию -v.
Продолжение
Дополнительные материалы pytest, которые помогут вам настроить тесты под ваш workflow:
“ How to invoke pytest” — примеры запуска из командной строки
“ How to use pytest with an existing test suite” — работа с уже существующим набором тестов
“ How to mark test functions with attributes” — механизм pytest.mark
“ Fixtures reference” — справочник по фикстурам
“ Writing plugins” — управление и написание плагинов
“ Good Integration Practices” — практики интеграции для virtualenv и структуры тестов