В Python сообществе, pytest наверно является самым популярным “запускателем” тестов и этому есть несколько причин:

  • активная разработка - релизы pytest выходят регулярно, добавляя новые фишки
  • гибкость - он не навязывает вам четкие правила, вы можете сами выбирать архитектуру тестов. Можно писать любые виды тестов, использовать BDD и т.п.
  • расширяемость - очень гибкая система плагинов, расширений и т.п.
  • уникальная фишка “фикстуры” - способ внедрения зависимостей в тесты

В этой статье, я бы хотел рассказать кратко про все эти вещи.

Активная разработка

Очень активный гитхаб, раз в 2-4 недели выпускают баг фикс релизы, в 6-9 месяцев релизы с новым функционалом. Активное сообщество с большим количеством pull requests и сообщений о багах

Гибкость

Py.test абсолютно не навязывает структуру тестов, как их располагать, как писать. Вы можете использовать pytest для запуска doctest (интересно, кто-то их использует?), писать тесты без классов, писать с классами, подключить и использовать BDD, писать unit тесты, UI тесты - в общем, никаких ограничений нет. Хранить свои файлы там же где тесты, в отдельной папке, любой иерархией. Также, вы можете написать свои расширения, которые будут улучшать assert проверки и делать кастомный вывод.

Расширяемость

В py.test можно выделить два механизма расширения:

  1. Свои фикстуры - небольшие функции, которые влияют непосредственно на тесты и окружение (например делающие setup/teardown)
  2. Плагины - очень гибкая система встраивания плагинов, которая позволяет повлиять на любой аспект работы py.test. Вы можете встроить что-то и повлиять на: кастомные флаги запуска, поиск тестов, генерация, запуск тестов, исполнение assert в тестах, обработка исключений, отображение результатов работы и т.п. Более подробно можно посмотреть тут

Фикстуры

Когда говорят py.test, то конечно вспоминают о фикстурах. Это такой способ в pytest реализовать setup и teardown вещи (запустить что-то перед и после тестов), но имеющий несколько особенностей:

  1. вы контролируете когда он должен запускаться - на каждый тест, один раз на модуль, один раз вообще
  2. одна функция может покрыть функционал “до” теста и “после”.
  3. фикстура может возвращать значение, которое будет передано в ваш тест

Покажу на примере, как это выглядит. Допустим, нам перед всеми тестами необходимо инициализировать базу данных, а перед каждым тестом создать в ней пользователя, а после - удалить:

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 будет разным (как объектом, так и значением).

О том, как работать с фикстурами более детально, расскажу в следующей статье