Статьи
April 17, 2023

Тестирование кода с pytest

Тестирование гарантирует предсказуемость поведения кода и даёт уверенность в том, что изменения не поломают всё к чертям. Написание и поддержка тестов — штука непростая, однако существуют инструменты, помогающие сделать процесс менее болезненным. pytest — один из них.

Установка pytest

Чтобы следовать некоторым примерам ниже, вам потребуется установить модуль pytest. Как и большинство пакетов, он доступен на PyPI. Вы можете установить его в виртуальном окружении с помощью команды pip:

Windows:
PS> python -m venv venv
PS> .\venv\Scripts\activate
(venv) PS> python -m pip install pytest

Linux/macOS:
$ python -m venv venv
$ source venv/bin/activate
(venv) $ python -m pip install pytest

Преимущества модуля

Если вы уже писали тесты для своего кода, то, возможно, использовали встроенный в Python модуль unittest. Это надёжная база для создания набора тестов, но у него есть несколько недостатков.

pytest, в свою очередь, — это многофункциональная экосистема для тестирования кода на Python, основанная на плагинах. С pytest обычные задачи требуют меньше кода, а продвинутые могут быть решены с применением различных команд и плагинов. Он даже запустит ваши существующие тесты, включая те, которые были написаны с помощью unittest.

Меньше шаблонов

Большинство функциональных тестов следуют модели «Организовать-Запустить-Утвердить»:

  • Организовать условия для проведения теста;
  • Запустить некоторую функцию или метод;
  • Утвердить истинность некоторого конечного условия.

Фреймворки для тестирования обычно подключаются к утверждениям (assertations) вашего теста, чтобы предоставить информацию, если оно не проходит. unittest, например, предоставляет ряд полезных утилит для утверждений из коробки. Однако даже небольшой набор тестов требует достаточно большого количества шаблонного кода.

Представьте, что вы хотите написать набор тестов, чтобы убедиться, что unittest работает правильно в вашем проекте. Тогда вы, вероятно, напишете один тест, который всегда проходит, и один, который всегда валится:

# тест с использованием unittest

from unittest import TestCase

class TryTesting(TestCase):
    def test_always_passes(self):
        self.assertTrue(True)

    def test_always_fails(self):
        self.assertTrue(False)

Затем вы можете запустить эти тесты из командной строки с помощью опции discover:

(venv) $ python -m unittest discover
F.
======================================================================
FAIL: test_always_fails (test_with_unittest.TryTesting)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "...\effective-python-testing-with-pytest\test_with_unittest.py",
  line 10, in test_always_fails
    self.assertTrue(False)
AssertionError: False is not true

----------------------------------------------------------------------

Ran 2 tests in 0.006s

FAILED (failures=1)

Как и ожидалось, один тест прошёл, а другой провалился. unittest работает, но посмотрите, что нам пришлось сделать:

  • импортировать класс TestCase из unittest;
  • создать TryTesting, подкласс класса TestCase;
  • написать метод в TryTesting для каждого теста;
  • использовать один из методов self.assert* из unittest.TestCase для создания ассертов.

Это дофига кода, который вы в итоге будете писать снова и снова. pytest упрощает этот процесс, позволяя использовать обычные функции и ключевое слово assert напрямую:

# тест с использованием pytest

def test_always_passes():
    assert True

def test_always_fails():
    assert False

Вот и всё. Вам не нужно иметь дело ни с импортом, ни с классами. Всё, что вам нужно сделать — это написать функцию с префиксом test_. Поскольку вы можете использовать ключевое слово assert, вам также не нужно изучать или запоминать все различные методы self.assert* в unittest. Если вы можете написать выражение, которое, как вы ожидаете, будет иметь значение True, pytest проверит его за вас.

Более удобный вывод

Вы можете запустить свой набор тестов с помощью команды pytest из папки верхнего уровня проекта:

(venv) $ pytest
========================== test session starts ============================
platform win32 -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0
rootdir: ...\effective-python-testing-with-pytest
collected 4 items

test_with_pytest.py .F                                               [ 50%]
test_with_unittest.py F.                                             [100%]

================================ FAILURES =================================
____________________________ test_always_fails ____________________________

    def test_always_fails():
>       assert False
E       assert False

test_with_pytest.py:7: AssertionError
______________________ TryTesting.test_always_fails _______________________

self = <test_with_unittest.TryTesting testMethod=test_always_fails>

    def test_always_fails(self):
>       self.assertTrue(False)
E       AssertionError: False is not true

test_with_unittest.py:10: AssertionError
======================== short test summary info ==========================
FAILED test_with_pytest.py::test_always_fails - assert False
FAILED test_with_unittest.py::TryTesting::test_always_fails - AssertionError:...

======================== 2 failed, 2 passed in 0.20s =======================

pytest представляет результаты тестирования иначе, чем unittest, и файл test_with_unittest.py также был автоматически включён. Отчёт показывает:

  • состояние системы, включая версии Python, pytest и любых установленных плагинов;
  • rootdir, или каталог, в котором следует искать конфигурацию и тесты;
  • количество тестов, обнаруженных программой.

Всё это содержится в первом разделе вывода:

=========================== test session starts ===========================
platform win32 -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0
rootdir: ...\effective-python-testing-with-pytest
collected 4 items

В выводе указывается статус каждого теста с использованием синтаксиса, аналогичного unittest:

  • точка (.) означает, что тест пройден;
  • знак F означает, что тест провален;
  • символ E означает, что тест вызвал неожиданное исключение.

Специальные символы отображаются рядом с именем, а общий ход выполнения набора тестов показан справа:

test_with_pytest.py .F                                               [ 50%]
test_with_unittest.py F.                                             [100%]

Проваленные тесты сопровождаются в отчёте подробным описанием причины неудачи тестирования. В примере тесты не сработали, потому что assert False всегда срабатывает:

================================ FAILURES =================================
____________________________ test_always_fails ____________________________

    def test_always_fails():
>       assert False
E       assert False

test_with_pytest.py:7: AssertionError
______________________ TryTesting.test_always_fails _______________________

self = <test_with_unittest.TryTesting testMethod=test_always_fails>

    def test_always_fails(self):
>       self.assertTrue(False)
E       AssertionError: False is not true

test_with_unittest.py:10: AssertionError

Этот дополнительный вывод может быть очень полезен при отладке. Наконец, вывод содержит общий отчёт о состоянии набора тестов:

========================= short test summary info =========================
FAILED test_with_pytest.py::test_always_fails - assert False
FAILED test_with_unittest.py::TryTesting::test_always_fails - AssertionError:...

======================= 2 failed, 2 passed in 0.20s =======================

По сравнению с unittest, результаты pytest гораздо более информативны и читабельны.

Далее поподробнее рассмотрим, как используется ключевого слова assert.

Меньше зубрёжки

Возможность использовать ключевое слово assert очень важна. Если вы уже использовали его раньше, то тут ничего нового не будет. Вот несколько примеров ассертов:

# пример ассерта

def test_uppercase():
    assert "loud noises".upper() == "LOUD NOISES"

def test_reversed():
    assert list(reversed([1, 2, 3, 4])) == [4, 3, 2, 1]

def test_some_primes():
    assert 37 in {
        num
        for num in range(2, 50)
        if not any(num % div == 0 for div in range(2, num))
    }

Выглядит как обычная функция. Обратите внимание, что каждый тест довольно маленький и самодостаточный. Вы видите длинные имена функций, а внутри них, по сути, мало что происходит. Это служит главным образом для того, чтобы ваши тесты были изолированы друг от друга, так что если что-то сломается, вы будете точно знать, где.

Проще управлять состоянием и зависимостями

Ваши тесты часто будут зависеть от типов данных или тестовых дублёров (они имитируют объекты, с которыми, скорее всего, столкнётся ваш код, например, словари или файлы JSON).

В unittest вы можете извлечь эти зависимости в методы .setUp() и .tearDown(), чтобы каждый тест в классе мог их использовать. Использование специальных методов — это хорошо, но по мере увеличения классов тестов вы можете непреднамеренно сделать зависимость совершенно неявной. Другими словами, рассматривая один из множества тестов в отдельности, вы можете не сразу заметить, что он зависит от чего-то ещё.

Со временем неявные зависимости приведут к сложному клубку кода, который вам придется разматывать, чтобы понять смысл ваших тестов. Тесты должны помочь сделать ваш код более понятным. Если сами тесты сложны для понимания, то у вас могут возникнуть проблемы.

pytest использует другой подход. Он ведёт вас к явному объявлению зависимостей, которые можно переиспользовать благодаря наличию фикстур. Фикстуры — это функции, которые могут создавать данные, тестовые двойники или инициализировать состояние системы для набора тестов. Любой тест с фикстурой должен явно использовать её в качестве аргумента тестовой функции, поэтому зависимости всегда указываются заранее:

# fixture_demo.py

import pytest

@pytest.fixture
def example_fixture():
    return 1

def test_with_fixture(example_fixture):
    assert example_fixture == 1

Глядя на тестовую функцию, можно сразу сказать, от чего она зависит, без необходимости проверять весь файл на наличие зависимостей.

Фикстуры могут также использовать другие фикстуры, явно объявляя их как зависимости. Это означает, что со временем они могут стать громоздкими. Хотя возможность делать огромную матрёшку из фикстур обеспечивает гибкость, она также может усложнить управление зависимостями по мере роста вашего набора тестов.

Проще фильтровать тесты

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

  • Фильтрация по имени. Вы можете ограничить pytest запуском только тех тестов, чьи полные имена соответствуют определённому выражению. Для этого есть параметр -k.
  • Ограничение по каталогам. По умолчанию pytest будет выполнять только те тесты, которые находятся в текущем каталоге или под ним.
  • Категоризация тестов. pytest может включать или исключать тесты из определённых категорий, которые вы зададите. Для этого есть параметр -m.

Категоризация — это мощный инструмент, а pytest позволяет создавать метки или пользовательские ярлыки. Далее будет пример работы меток, и вы узнаете, как использовать их в большом наборе тестов.

Возможность параметризации

Когда вы тестируете функции, обрабатывающие данные или выполняющие общие преобразования, то рано или поздно осознаёте, что пишете много похожих один на другой тестов. Это приводит к дублированию кода, что может затушевать поведение, которое вы пытаетесь протестировать.

unittest предлагает способ собрать несколько тестов в один, но они не отображаются как отдельные тесты в отчётах о результатах. Если один тест провалится, а остальные пройдут, то вся группа вернёт один неудачный результат. pytest предлагает собственное решение, в котором каждый тест может пройти или провалиться независимо.

Архитектура на основе плагинов

Одной из самых прекрасных особенностей pytest является его открытость к настройке и новым возможностям. Почти каждый элемент программы может быть изменён, как результат — пользователи pytest создали богатую экосистему полезных плагинов. И хотя некоторые из них ориентированы на конкретные фреймворки, такие как Django, другие применимы к большинству тестовых наборов.

Фикстуры: управление состоянием и зависимостями

Фикстуры — это функции, которые могут возвращать широкий диапазон значений. Каждый тест, зависящий от фикстуры, должен явно принимать её в качестве аргумента.

Когда создавать фикстуры

В этом разделе смоделируем типичный рабочий процесс разработки, управляемой тестами (test-driven development или TDD).

Представьте, что вы пишете функцию format_data_for_display() для обработки данных, возвращаемых конечной точкой API. Данные представляют собой список людей, каждый из которых имеет имя, фамилию и должность. Функция должна вывести список строк, включающий полное имя каждого человека (given_name, затем family_name), двоеточие и должность:

# format_data.py

def format_data_for_display(people):
    ... # Реализовать!

Если по-хорошему, то сначала нужно написать тест. Для этого можно написать следующий код:

# test_format_data.py

def test_format_data_for_display():
    people = [
        {
            "given_name": "Alfonsa",
            "family_name": "Ruiz",
            "title": "Senior Software Engineer",
        },
        {
            "given_name": "Sayid",
            "family_name": "Khan",
            "title": "Project Manager",
        },
    ]

    assert format_data_for_display(people) == [
        "Alfonsa Ruiz: Senior Software Engineer",
        "Sayid Khan: Project Manager",
    ]

Пока пишете тест, понимаете, что вам может потребоваться написать ещё одну функцию для преобразования данных в значения, разделенные запятыми, для использования в Excel:

# format_data.py

def format_data_for_display(people):
    ...  # Реализовать!

def format_data_for_excel(people):
    ... # Реализовать!

Список доработок растёт, и это хорошо! Одно из преимуществ TDD в том, что оно помогает планировать работу наперед. Тест для функции format_data_for_excel() будет выглядеть ужасно похоже на тест для функции format_data_for_display():

# test_format_data.py

def test_format_data_for_display():
    # ...

def test_format_data_for_excel():
    people = [
        {
            "given_name": "Alfonsa",
            "family_name": "Ruiz",
            "title": "Senior Software Engineer",
        },
        {
            "given_name": "Sayid",
            "family_name": "Khan",
            "title": "Project Manager",
        },
    ]

    assert format_data_for_excel(people) == """given,family,title
Alfonsa,Ruiz,Senior Software Engineer
Sayid,Khan,Project Manager
"""

Оба теста должны повторять определение переменной people, а это довольно много строк кода.

Если вы пишете несколько тестов, в которых используются одни и те же базовые тестовые данные, возможно, вам стоит использовать фикстуру. Вы можете собрать повторяющиеся данные в одну функцию, с декоратором @pytest.fixture, чтобы указать, что функция является фикстурой pytest:

# test_format_data.py

import pytest

@pytest.fixture
def example_people_data():
    return [
        {
            "given_name": "Alfonsa",
            "family_name": "Ruiz",
            "title": "Senior Software Engineer",
        },
        {
            "given_name": "Sayid",
            "family_name": "Khan",
            "title": "Project Manager",
        },
    ]

# ...

Вы можете использовать фикстуру, добавляя ссылку на функцию в качестве аргумента в ваши тесты. Обратите внимание, что вы не вызываете функцию фикстуры, об этом позаботится pytest. Вы сможете использовать возвращаемое значение функции фикстуры в качестве её имени:

# test_format_data.py

# ...

def test_format_data_for_display(example_people_data):
    assert format_data_for_display(example_people_data) == [
        "Alfonsa Ruiz: Senior Software Engineer",
        "Sayid Khan: Project Manager",
    ]

def test_format_data_for_excel(example_people_data):
    assert format_data_for_excel(example_people_data) == """given,
    family,title
    Alfonsa,Ruiz,Senior Software Engineer
    Sayid,Khan,Project Manager
    """

Каждый тест стал заметно короче, но при этом имеет чёткий путь к данным, от которых он зависит. Когда вы впервые открываете для себя возможности фикстур, может возникнуть соблазн использовать их постоянно, но, как и во всём, необходимо соблюдать баланс.

Когда следует избегать фикстур

Фикстуры отлично подходят для извлечения данных или объектов, которые вы используете в нескольких тестах. Однако они не всегда подходят для тестов, в которых требуется незначительная вариация данных. Засорять свой набор тестов фикстурами не лучше, чем засорять его обычными данными или объектами.

Как и в случае с большинством абстракций, требуется определённая практика и размышления, чтобы найти баланс в использования фикстур. По мере роста проекта возникает проблема масштаба. В pytest есть функции, которые помогут вам справиться со сложностями при масштабировании проекта.

Масштабирование и фикстуры

В pytest фикстуры являются модульными. Это означает, что они могут быть импортированы, а также сами могут импортировать другие модули и зависеть от других фикстур. Всё это позволяет составить подходящую абстракцию фикстуры для какого-то конкретного теста.

Например, вы замечаете, что фикстуры в двух отдельных файлах или модулях имеют общую зависимость. В этом случае можно переместить фикстуры из тестовых модулей в более общие модули, связанные с фикстурами. Таким образом, вы сможете импортировать их обратно в любые тестовые модули, которым они нужны.

Сделать фикстуры доступным для всего вашего проекта без импорта можно с помощью специального модуля конфигурации conftest.py.

pytest ищет модуль conftest.py в каждом каталоге. Если вы добавите свои фикстуры общего назначения в модуль, то сможете использовать их во всём родительском каталоге модуля и в любых подкаталогах без необходимости импорта.

Ещё один интересный случай использования фикстур и conftest.py — защита доступа к ресурсам. Представьте, что вы написали набор тестов для кода, который работает с вызовами API. Вы хотите убедиться, что набор тестов не совершает реальных сетевых вызовов, даже если кто-то случайно напишет тест, который это сделает. В pytest есть фикстура monkeypatch для замены значений и поведения:

# conftest.py

import pytest
import requests

@pytest.fixture(autouse=True)
def disable_network_calls(monkeypatch):
    def stunted_get():
        raise RuntimeError("Network access not allowed during testing!")
    monkeypatch.setattr(requests, "get", 
    lambda *args, **kwargs: stunted_get())

Поместив disable_network_calls() в conftest.py и добавив опцию autouse=True, вы гарантируете, что сетевые вызовы будут отключены во всех тестах набора. Любой тест, выполняющий код и вызывающий requests.get(), выдаст ошибку RuntimeError. Она указывает на то, что произошёл непредвиденный сетевой вызов.

Итак, количество тестов у вас растёт, и в месте с ними растёт и время, затраченное на их выполнение. В таких случаях вы, возможно, захотите ограничиться запуском какой-то определённой категорией тестов.

Марки: категоризация тестов

В любом большом проекте было бы неплохо избежать запуска всех тестов, когда вы пытаетесь быстро и итеративно доработать новую функцию. Помимо стандартных фич pytest по запуску всех тестов в текущем рабочем каталоге или функции фильтрации, вы можете воспользоваться маркерами.

pytest позволяет определять категории для ваших тестов и предоставляет опции для их включения или исключения при запуске кода. Тест можно пометить любым количеством категорий.

Такие пометки полезны для категоризации тестов по подсистемам или зависимостям. Например, если некоторые из ваших тестов требуют доступа к базе данных, вы можете создать для них пометку @pytest.mark.database_access.

Совет: Поскольку вы можете давать своим меткам любые имена, легко ошибиться или не запомнить имя метки. pytest предупредит вас, если не распознает некоторые из них в результатах тестирования. Вы можете использовать флаг --strict-markers в команде pytest, чтобы убедиться, что все метки в ваших тестах зарегистрированы в конфигурационном файле pytest.ini. Это не позволит вам запустить ваши тесты, пока вы не зарегистрируете неизвестные метки. Более подробную информацию о регистрации меток можно найти в документации по pytest.

Когда придет время запускать тесты, вы можете запустить их все по умолчанию с помощью команды pytest. Если вы хотите запустить только те тесты, которые требуют доступа к базе данных, используйте команду pytest -m database_access. Чтобы запустить все тесты, кроме тех, которые требуют доступа к базе данных, используйте pytest -m "not database_access". Можете также использовать autouse fixture, чтобы ограничить доступ к базе данных только теми тестами, которые помечены database_access.

Некоторые плагины расширяют функциональность меток, добавляя свои собственные методы защиты. Например, у плагина pytest-django есть метка django_db. Все тесты без этой метки, которые пытаются получить доступ к базе данных, будут провалены. Первый тест, который попытается получить доступ к БД, запустит создание тестовой базы данных Django.

Требование добавить метку django_db подталкивает вас к явному указанию зависимостей. Так или иначе, вы сможете быстрее выполнять тесты, которые не зависят от БД, потому что pytest -m "not django_db" не позволит тесту инициировать создание базы данных. Экономия времени действительно увеличивается, особенно если вы часто что-то тестируете.

В pytest есть несколько встроенных меток:

  • skip беговорочно пропускает тест;
  • skipif пропускает тест, если переданное ему выражение оценивается как True;
  • xfail указывает, что ожидается провал теста, так что если тест провалится, то общий набор все равно будет иметь статус пройденного;
  • parametrize создаёт несколько вариантов теста с различными значениями в качестве аргументов.

Весь список меток можно посмотреть, выполнив команду pytest --markers.

Параметризация: объединение тестов

Ранее мы посмотрели, как можно использовать фикстуры для переиспользования кода за счёт извлечения общих зависимостей. Фикстуры не так полезны, когда у вас есть несколько тестов с немного разными входами и ожидаемыми выходами. В этих случаях можно параметризовать определение одного теста, и pytest создаст для вас варианты с указанными вами параметрами.

Представьте, что вы написали функцию для определения того, является ли строка палиндромом. Начальный набор тестов может выглядеть следующим образом:

def test_is_palindrome_empty_string():
    assert is_palindrome("")

def test_is_palindrome_single_character():
    assert is_palindrome("a")

def test_is_palindrome_mixed_casing():
    assert is_palindrome("Bob")

def test_is_palindrome_with_spaces():
    assert is_palindrome("Never odd or even")

def test_is_palindrome_with_punctuation():
    assert is_palindrome("Do geese see God?")

def test_is_palindrome_not_palindrome():
    assert not is_palindrome("abc")

def test_is_palindrome_not_quite():
    assert not is_palindrome("abab")

Все эти тесты, кроме двух последних, имеют одинаковую форму:

def test_is_palindrome_<in some situation>():
    assert is_palindrome("<some string>")

Так используйте @pytest.mark.parametrize(), чтобы заполнить эту форму различными значениями, значительно сократив код:

@pytest.mark.parametrize("palindrome", [
    "",
    "a",
    "Bob",
    "Never odd or even",
    "Do geese see God?",
])
def test_is_palindrome(palindrome):
    assert is_palindrome(palindrome)

@pytest.mark.parametrize("non_palindrome", [
    "abc",
    "abab",
])
def test_is_palindrome_not_palindrome(non_palindrome):
    assert not is_palindrome(non_palindrome)

Первым аргументом функции parametrize() является строка имён параметров, разделенная запятыми. Как видно из примера, необязательно указывать более одного имени. Второй аргумент — это список либо кортежей, либо одиночных значений, которые представляют значение(я) параметра(ов). И можно объединить все тесты в один:

@pytest.mark.parametrize("maybe_palindrome, expected_result", [
    ("", True),
    ("a", True),
    ("Bob", True),
    ("Never odd or even", True),
    ("Do geese see God?", True),
    ("abc", False),
    ("abab", False),
])
def test_is_palindrome(maybe_palindrome, expected_result):
    assert is_palindrome(maybe_palindrome) == expected_result

Убедитесь, однако, что не параметризуете свой набор тестов до состояния, когда разобраться в коде невозможно. Используйте параметризацию для отделения тестовых данных от поведения теста, чтобы было понятно, что именно тестируется, а также для того, чтобы различные тестовые случаи было легче читать и поддерживать.

Отчёты о длительности: борьба с медленными тестами

Каждый раз, когда мы переключаем контекст с кода реализации на код тестирования, вы затрачиваете дополнительное время. Если тесты изначально медленные, то и потеря времени будет велика.

Выше мы писали об использовании меток для отсеивания медленных тестов при запуске набора, но в какой-то момент вам придётся их запускать. Если вы хотите увеличить скорость работы тестов, то полезно знать, какие тесты работают медленно, а какие — нет. pytest, кстати, может автоматически записывать длительность отработки тестов.

Используйте команду --durations, чтобы включить отчёт о длительности в результаты тестирования. Она сообщит о самом медленном n-ном количестве тестов. Новый раздел будет включён в ваш отчет о тестировании:

(venv) $ pytest --durations=5
...
=========================== slowest 5 durations ===========================
3.03s call     test_code.py::test_request_read_timeout
1.07s call     test_code.py::test_request_connection_timeout
0.57s call     test_code.py::test_database_read

(2 durations < 0.005s hidden.  Use -vv to show these durations.)
========================= short test summary info =========================
...

Каждый тест, который отображается в отчёте, является хорошим кандидатом на ускорение. Обратите внимание, что быстрые тесты скрыты по умолчанию. Вы можете увеличить точность отчёта и показать их, передав -vv вместе с --durations.

Имейте в виду, что некоторые тесты могут иметь не совсем очевидное доп время на исполнение. Первый тест, помеченный django_db, запускает создание тестовой базы данных Django, если ещё помните. Отчёт о длительности отражает время, необходимое для установки БД в тесте, который вызвал создание этой самой БД, а это может ввести в заблуждение.

Полезные плагины

pytest-randomly

Часто порядок следования тестов не имеет значения, но можно столько всего накрутить в коде, что некоторые тесты провалятся, если их запустить не по порядку. pytest-randomly заставляет ваши тесты запускаться в случайном порядке, просто перемешивает список перед выполнением.

Отличный способ выявить тесты, которые имеют зависимость от состояния какого-то другого теста. Если вы создали свой набор тестов с нуля в pytest, то вероятность этого невелика. Более вероятно, что это произойдёт в наборах тестов, которые вы перенесли в pytest.

pytest-cov

Если вы хотите измерить, насколько хорошо ваши тесты покрывают код реализации, то вы можете использовать pytest-cov. Этот плагин покажет отчёт о покрытии тестами, и вы сможете похвастаться им на первой странице вашего проекта.

pytest-django

pytest-django предоставляет несколько полезных фикстур и меток для работы с тестами Django. Про метку django_db уже сказали. Фикстура rf предоставляет прямой доступ к экземпляру фабрики запросов Django RequestFactory. Фикстура settings предоставляет быстрый способ установить или переопределить настройки Django.

pytest-bdd

Поведенчески-ориентированная разработка (BDD) поощряет создание описаний на простом языке вероятных действий и ожиданий пользователя, которые затем можно использовать для определения необходимости реализации той или иной функции. pytest-bdd поможет вам использовать Gherkin для написания функциональных тестов для вашего кода.

Заключение

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

Теоретически, вы готовы пойти и затестировать весь свой код. Вперёд!

👉🏻Подписывайтесь на PythonTalk в Telegram 👈🏻

👨🏻‍💻Чат PythonTalk в Telegram💬

🍩 Поддержать канал 🫶

Источник: Real Python
Перевод и адаптация: Екатерина Прохорова