Эффективное тестирование на Python с помощью pytest
Оглавление
- Как установить pytest
- Что делает pytest таким полезным?
- Светильники: Управление состоянием и зависимостями
- Оценки: Классификация тестов
- Параметризация: Комбинирование тестов
- Отчеты о продолжительности: Борьба с медленными тестами
- Полезные плагины для 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”. По завершении вы получите оценку, которая поможет вам отслеживать прогресс в обучении:
<время работы/>
Интерактивная викторина
Эффективное тестирование с помощью 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для обеспечения эффективности вашего тестирования даже при его масштабировании.Меньше шаблонов
Большинство функциональных тестов выполняются по модели "Организовать-действовать-утверждать":
- Организовать или настроить условия для проведения теста
- Действуйте, вызывая какую-либо функцию или метод
- Утверждать, что некоторое конечное условие истинно
Тестовые платформы обычно подключаются к утверждениям вашего теста, чтобы они могли предоставлять информацию в случае сбоя утверждения.
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работает, но посмотрите, что вам пришлось сделать:
- Импортируйте класс
TestCaseизunittest- Создать
TryTesting, подкласс изTestCase- Напишите метод в
TryTestingдля каждого теста- Используйте один из
self.assert*методов изunittest.TestCaseдля создания утвержденийЭто значительный объем кода для написания, и поскольку это минимум, необходимый для любого теста, в конечном итоге вам придется писать один и тот же код снова и снова.
pytestупрощает этот рабочий процесс, позволяя вам напрямую использовать обычные функции и ключевое слово Pythonassert: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также был включен автоматически. В отчете показано:
- Состояние системы, включая версии 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гораздо более информативен и удобочитаем.В следующем разделе вы более подробно рассмотрите, как
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'sRequestFactory. Модуль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упрощает рабочий процесс тестирования, позволяя использовать обычные функции и ключевое слово Pythonassertвместо того, чтобы полагаться на классы и специальные методы подтверждения, требуемыеunittest. Он также предоставляет более подробные выходные данные, поддерживает приспособления для управления зависимостями и обладает богатой экосистемой плагинов, что делает его более гибким и простым в масштабировании, чемunittest.
В
pytestвы можете управлять зависимостями тестов и состоянием с помощью фиксированных параметров. Фиксированные параметры - это функции, которые возвращают данные, дублируют тесты или инициализируют состояние системы и объявляются как зависимости в ваших тестовых функциях. Это гарантирует, что зависимости являются явными и могут быть повторно использованы в вашем наборе тестов.
Вы можете использовать
@pytest.mark.parametrizeдекоратор для запуска одной тестовой функции с разными наборами параметров. Это позволяет тестировать несколько сценариев ввода-вывода без дублирования тестового кода, что делает ваши тесты более краткими и простыми в обслуживании.<отметить класс="marker-выделить"> Пройдите тест: отметьте> Проверьте свои знания с помощью нашей интерактивной викторины “Эффективное тестирование с помощью 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