Эффективное тестирование на Python с помощью pytest

Оглавление

Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Тестирование вашего кода с помощью pytest

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

К концу этого урока вы поймете, что:

  • Для использования pytest требуется установить его с помощью pip в виртуальной среде для настройки команды pytest.
  • pytest позволяет использовать меньше кода, облегчает чтение и предоставляет больше возможностей по сравнению с unittest.
  • Управление тестовыми зависимостями и состоянием с помощью pytest становится эффективным благодаря использованию fixtures, которые предоставляют явные объявления зависимостей.
  • Параметризация в pytest помогает избежать избыточности тестового кода, позволяя использовать несколько сценариев тестирования из одной тестовой функции.
  • Самоанализ утверждений в pytest содержит подробную информацию о сбоях в отчете о тестировании.

Бесплатный бонус: 5 Размышления о мастерстве владения Python - бесплатный курс для разработчиков Python, который показывает вам план действий и мышление, с которым вы будете работать. вам нужно поднять свои навыки работы с Python на новый уровень.

<отметить класс="marker-выделить"> Пройдите тест: Проверьте свои знания с помощью нашей интерактивной викторины “Эффективное тестирование с помощью Pytest”. По завершении вы получите оценку, которая поможет вам отслеживать прогресс в обучении:

<время работы/> Effective Python Testing With Pytest

Интерактивная викторина

Эффективное тестирование с помощью Pytest

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

Как установить pytest

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

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



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


Команда pytest теперь будет доступна в вашей среде установки.

Что делает pytest Таким полезным?

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

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

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

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

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

Большинство функциональных тестов выполняются по модели "Организовать-действовать-утверждать":

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

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

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

test_with_unittest.py
from unittest import TestCase

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

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

Затем вы можете запустить эти тесты из командной строки, используя discover параметр unittest:

(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 работает, но посмотрите, что вам пришлось сделать:

  1. Импортируйте класс TestCase из unittest
  2. Создать TryTesting, подкласс из TestCase
  3. Напишите метод в TryTesting для каждого теста
  4. Используйте один из self.assert* методов из unittest.TestCase для создания утверждений

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

test_with_pytest.py
def test_always_passes():
    assert True

def test_always_fails():
    assert False

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

В 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 также был включен автоматически. В отчете показано:

  1. Состояние системы, включая версии Python, pytest и все установленные вами плагины
  2. rootdir или каталог, в котором выполняется поиск конфигурации и тестов
  3. Количество тестов, обнаруженных пользователем

Эти элементы представлены в первом разделе выходных данных:

============================= 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 гораздо более информативен и удобочитаем.

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

Учиться осталось меньше

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

test_assert_examples.py
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))
    }

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

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

Чтобы увидеть пример проекта, который создает набор тестов вместе с основным проектом, ознакомьтесь с руководством Создание хэш-таблицы на Python с помощью TDD. Кроме того, вы можете поработать над практическими задачами на Python, чтобы самостоятельно попробовать разработку на основе тестов, пока вы готовитесь к следующему собеседованию или разбираете CSV-файлы.

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

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

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

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

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

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

fixture_demo.py
import pytest

@pytest.fixture
def example_fixture():
    return 1

def test_with_fixture(example_fixture):
    assert example_fixture == 1

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

Примечание: Обычно вы хотите поместить свои тесты в отдельную папку с именем tests на корневом уровне вашего проекта.

Для получения дополнительной информации о структурировании приложения на Python ознакомьтесь с видеокурсом, посвященным этой теме.

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

Далее в этом руководстве вы узнаете больше о приспособлениях и попробуете несколько методов решения этих проблем.

Легко фильтруемые тесты

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

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

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

Позволяет выполнить параметризацию теста

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

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

Имеет архитектуру, основанную на плагинах

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

Хотя некоторые pytest плагины ориентированы на конкретные фреймворки, такие как Django, другие применимы к большинству наборов тестов. Вы увидите подробную информацию о некоторых конкретных плагинах далее в этом руководстве.

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

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

Когда создавать светильники

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

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

format_data.py
def format_data_for_display(people):
    ...  # Implement this!

В соответствии с правилами TDD, вам нужно сначала написать тест для этого. Для этого вы могли бы написать следующий код:

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):
    ...  # Implement this!

def format_data_for_excel(people):
    ...  # Implement this!

Ваш список дел растет! Это хорошо! Одним из преимуществ 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",
        },
    ]

# ...

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

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, то сможете использовать их в родительском каталоге модуля и в любых подкаталогах без необходимости их импорта. Это отличное место для размещения наиболее часто используемых светильников.

Другой интересный вариант использования fixtures и 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, pytest.ini. Это не позволит вам запускать тесты до тех пор, пока вы не зарегистрируете какие-либо неизвестные метки.

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

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

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

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

pytest содержит несколько дополнительных пометок:

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

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

Что касается параметризации, то мы поговорим об этом позже.

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

Ранее в этом руководстве вы видели, как pytest можно использовать фикстуры для уменьшения дублирования кода путем извлечения общих зависимостей. Фикстуры не так полезны, когда у вас есть несколько тестов со слегка отличающимися входными данными и ожидаемыми выходными данными. В этих случаях вы можете параметризовать одно определение теста, и 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 до сих пор это помогало вам избавиться от шаблонности, и теперь это вас не подведет. Вы можете использовать @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 для команды pytest, чтобы включить отчет о продолжительности в результаты теста. --durations ожидает целочисленное значение n и сообщит о самом медленном 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. Отчет durations отражает время, необходимое для настройки базы данных в тесте, который запустил создание базы данных, что может ввести в заблуждение.

Вы уже на пути к полному тестированию. Далее вы познакомитесь с некоторыми плагинами, которые являются частью богатой экосистемы плагинов pytest.

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

Ранее в этом руководстве вы узнали о нескольких полезных pytest плагинах. В этом разделе вы познакомитесь с этими и некоторыми другими плагинами более подробно — от служебных плагинов, таких как pytest-randomly, до библиотечных, например, для Django.

pytest-randomly

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

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

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

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

pytest-cov

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

pytest-django

pytest-django содержит несколько полезных настроек и отметок для работы с тестами Django. Ранее в этом руководстве вы видели отметку django_db. Модуль rf предоставляет прямой доступ к экземпляру Django's RequestFactory. Модуль settings предоставляет быстрый способ установки или переопределения настроек Django. Эти плагины значительно повысят производительность вашего тестирования на Django!

Если вам интересно узнать больше об использовании pytest в Django, ознакомьтесь с Как обеспечить тестирование моделей Django в Pytest.

pytest-bdd

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

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

Заключение

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

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

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

Установите pytest и попробуйте. Вы будете рады, что сделали это. Приятного тестирования!

Если вы ищете пример проекта, созданного с помощью pytest, ознакомьтесь с руководством по созданию хэш-таблицы с помощью TDD, которое не только поможет вам ускорьте работу с pytest, а также помогите вам освоить хэш-таблицы!

Часто задаваемые вопросы

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

Эти часто задаваемые вопросы относятся к наиболее важным понятиям, которые вы рассмотрели в этом руководстве. Нажмите на переключатель Показывать/скрывать рядом с каждым вопросом, чтобы открыть ответ.

Вы можете установить pytest с помощью pip в виртуальной среде, выполнив команду python -m pip install pytest. После установки вы можете запустить свой набор тестов, используя команду pytest из папки верхнего уровня вашего проекта.

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

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

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

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

<отметить класс="marker-выделить"> Пройдите тест: Проверьте свои знания с помощью нашей интерактивной викторины “Эффективное тестирование с помощью Pytest”. По завершении вы получите оценку, которая поможет вам отслеживать прогресс в обучении:

<время работы/> Effective Python Testing With Pytest

Интерактивная викторина

Эффективное тестирование с помощью Pytest

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

<статус завершения article-slug="pytest-python-тестирование" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/pytest-python-тестирование/закладка/" data-api-article-завершение-статус-url="/api/версия 1/статьи/pytest-python-тестирование/завершение_статуса/"> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0aэффективное тестирование Python с помощью pytest" email-subject="Статья о Python для вас" twitter-text="Интересная статья о Python от @realpython:" url="https://realpython.com/pytest-python-testing /" url-title="Эффективное тестирование на Python с помощью pytest">

Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Тестирование вашего кода с помощью pytest

Back to Top