В Python сообществе, pytest наверно является самым популярным “запускателем” тестов и этому есть несколько причин:
- активная разработка - релизы pytest выходят регулярно, добавляя новые фишки
- гибкость - он не навязывает вам четкие правила, вы можете сами выбирать архитектуру тестов. Можно писать любые виды тестов, использовать BDD и т.п.
- расширяемость - очень гибкая система плагинов, расширений и т.п.
- уникальная фишка “фикстуры” - способ внедрения зависимостей в тесты
В этой статье, я бы хотел рассказать кратко про все эти вещи.
Активная разработка
Очень активный гитхаб, раз в 2-4 недели выпускают баг фикс релизы, в 6-9 месяцев релизы с новым функционалом. Активное сообщество с большим количеством pull requests и сообщений о багах
Гибкость
Py.test абсолютно не навязывает структуру тестов, как их располагать, как писать. Вы можете использовать pytest для запуска doctest (интересно, кто-то их использует?), писать тесты без классов, писать с классами, подключить и использовать BDD, писать unit тесты, UI тесты - в общем, никаких ограничений нет. Хранить свои файлы там же где тесты, в отдельной папке, любой иерархией. Также, вы можете написать свои расширения, которые будут улучшать assert проверки и делать кастомный вывод.
Расширяемость
В py.test можно выделить два механизма расширения:
- Свои фикстуры - небольшие функции, которые влияют непосредственно на тесты и окружение (например делающие setup/teardown)
- Плагины - очень гибкая система встраивания плагинов, которая позволяет повлиять на любой аспект работы py.test. Вы можете встроить что-то и повлиять на: кастомные флаги запуска, поиск тестов, генерация, запуск тестов, исполнение assert в тестах, обработка исключений, отображение результатов работы и т.п. Более подробно можно посмотреть тут
Фикстуры
Когда говорят py.test, то конечно вспоминают о фикстурах. Это такой способ в pytest реализовать setup и teardown вещи (запустить что-то перед и после тестов), но имеющий несколько особенностей:
- вы контролируете когда он должен запускаться - на каждый тест, один раз на модуль, один раз вообще
- одна функция может покрыть функционал “до” теста и “после”.
- фикстура может возвращать значение, которое будет передано в ваш тест
Покажу на примере, как это выглядит. Допустим, нам перед всеми тестами необходимо инициализировать базу данных, а перед каждым тестом создать в ней пользователя, а после - удалить:
import pytest
import sqlite3
@pytest.fixture(scope="session")
def init_db():
db = sqlite3.connect("db.sqlite")
yield db
db.close()
@pytest.fixture()
def new_user(db):
username = generate_username()
cur = db.cursor()
cur.execute("INSERT INTO ...")
yield username
cur.execute("DELETE FROM ...")
def test_user1(new_user):
print(new_user)
def test_user2(new_user):
print(new_user)
В этом примере функция init_db выполнится только один, до yield db перед всеми тестами и после yield, как все тесты запустятся. А вот функция new_user, будет вызываться только столько раз, сколько раз передана в какую-либо тестовую функцию, в нашем случае два раза. Если мы добавим еще какой-то тест, но в него не передадим new_user, то третий раз она не исполнится.
Как видно из примера, фикстуры могут использоваться в фикстурах, а не только в тестах. Если вы запустите такой код (конечно доведя его до работающего состояния), то вы увидете, что в каждом из тестов new_user будет разным (как объектом, так и значением).
О том, как работать с фикстурами более детально, расскажу в следующей статье