Python testing с pytest. плагины, глава 5
Содержание:
- Создание устанавливаемого плагина
- Changelog
- Reporting
- TestList
- Usage
- Durations Reports: Fighting Slow Tests
- Выбор выполняемых тестов¶
- Creating JUnitXML format files¶
- Создание файлов формата JUnit¶
- Упражнения
- Starter Class
- Запуск отладчика PDB (Python Debugger) при падении тестов¶
- assert¶
- Фикстуры: управление состоянием и зависимостями
- Параметризация: комбинированные тесты
- Specifying tests / selecting tests¶
- Поехали дальше!
- Bonus: BDD syntax
- Conclusion
- Features
- Детализация сводного отчета¶
- Breaking Changes
- Предотвращение конфликтов имен файлов
- What about authentication?
- TestRetrieve
- Detailed summary report¶
- Warning about unraisable exceptions and unhandled thread exceptions¶
- Установка плагинов
- Вызов pytest из кода Python¶
- Parametrization: Combining Tests
- Calling pytest from Python code¶
- Putting it all together
- Что такое pytest?
- Dropping to PDB (Python Debugger) on failures¶
Создание устанавливаемого плагина
Процесс обмена плагинами с другими пользователями четко определен. Даже если вы никогда не включите свой собственный плагин в PyPI, то пройдя через этот процесс, вам будет легче читать код из плагинов с открытым исходным кодом, и у вас будет больше возможностей оценить, помогут они вам или нет.
Было бы излишним полностью охватывать packaging и distribution пакетов Python в этой книге, так как эта тема хорошо документирована в другом месте. Тут и здесь и еще здесь на русском. Тем не менее, перейти от локального подключаемого модуля конфигурации, который мы создали в предыдущем разделе, к чему-то устанавливаемому с помощью pip, является несложной задачей. ,
Во-первых, нам нужно создать новый каталог для размещения нашего кода плагина
Неважно, как вы это называете, но, поскольку мы создаем плагин для флага «nice», давайте назовем его «pytest-nice». У нас будет два файла в этом новом каталоге: pytest_nice.py и setup.py
(Каталог тестов будет обсуждаться в разделе «Плагины тестирования» на странице.105.)
В , мы поместим точное содержимое нашего conftest.py, которое было связано с этой функцией (и извлечем его из ):
ch5/pytest-nice/pytest_nice.py
В нам нужен максимальноминимальный вызов :
Вам понадобится больше информации в настройках, если вы собираетесь распространять ее среди широкой аудитории или в интернете. Однако для небольшой команды или просто для себя этого будет достаточно.
До сих пор все параметры являются стандартными и используются для всех инсталляторов Python. Частью, которая отличается для плагинов Pytest, является параметр . Мы перечислили Функция является стандартной для , но pytest11 специальный идентификатор, который ищет pytest. В этой строке мы сообщаем pytest, что -это имя нашего плагина, а -имя модуля, в котором живет наш плагин. Если бы мы использовали пакет, наша запись здесь была бы:
Я еще не говорил о файле . Некоторая форма README является требованием setuptools. Если вы пропустите его, вы получите это:
Сохранение README в качестве стандартного способа включения некоторой информации о проекте-хорошая идея в любом случае. Вот что я положил в файл для pytest-nice:
Есть много мнений о том, что должно быть в файле README. Это сильно обрезанная версия, но она работает.
Changelog
2.2.0 (2015-10-04)
- Added support for changing working directory in tests. Previously changing working
directory would disable coverage measurements in suprocesses. - Fixed broken handling for --cov-report=annotate.
2.0.0 (2015-07-28)
- Added --cov-fail-under, akin to the new fail_under option in coverage-4.0
(automatically activated if there’s a fail_under = ... in .coveragerc). - Changed --cov-report=term to automatically upgrade to --cov-report=term-missing
if there’s show_missing = True in .coveragerc. - Changed --cov so it can be used with no path argument (in wich case the source
settings from .coveragerc will be used instead). - Fixed .pth installation to work in all cases (install, easy_install, wheels, develop etc).
- Fixed .pth uninstallation to work for wheel installs.
- Support for coverage 4.0.
- Data file suffixing changed to use coverage’s data_suffix=True option (instead of the
custom suffixing). - Avoid warning about missing coverage data (just like coverage.control.process_startup).
- Fixed a race condition when running with xdist (all the workers tried to combine the files).
It’s possible that this issue is not present in pytest-cov 1.8.X.
Reporting
It is possible to generate any combination of the reports for a single test run.
The available reports are terminal (with or without missing line numbers shown), HTML, XML and
annotated source code.
The terminal report without line numbers (default):
py.test --cov-report term --cov=myproj tests/ -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover ---------------------------------------- myproj/__init__ 2 0 100% myproj/myproj 257 13 94% myproj/feature4286 94 7 92% ---------------------------------------- TOTAL 353 20 94%
The terminal report with line numbers:
py.test --cov-report term-missing --cov=myproj tests/ -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover Missing -------------------------------------------------- myproj/__init__ 2 0 100% myproj/myproj 257 13 94% 24-26, 99, 149, 233-236, 297-298, 369-370 myproj/feature4286 94 7 92% 183-188, 197 -------------------------------------------------- TOTAL 353 20 94%
The terminal report with skip covered:
py.test --cov-report term:skip-covered --cov=myproj tests/ -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover ---------------------------------------- myproj/myproj 257 13 94% myproj/feature4286 94 7 92% ---------------------------------------- TOTAL 353 20 94% 1 files skipped due to complete coverage.
You can use skip-covered with term-missing as well. e.g. --cov-report term-missing:skip-covered
These three report options output to files without showing anything on the terminal:
py.test --cov-report html --cov-report xml --cov-report annotate --cov=myproj tests/
The output location for each of these reports can be specified. The output location for the XML
report is a file. Where as the output location for the HTML and annotated source code reports are
directories:
py.test --cov-report html:cov_html --cov-report xml:cov.xml --cov-report annotate:cov_annotate --cov=myproj tests/
The final report option can also suppress printing to the terminal:
py.test --cov-report= --cov=myproj tests/
TestList
Usage
Centralised Testing
Centralised testing will report on the combined coverage of the main process and all of its
subprocesses.
Running centralised testing:
py.test --cov=myproj tests/
Shows a terminal report:
-------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover ---------------------------------------- myproj/__init__ 2 0 100% myproj/myproj 257 13 94% myproj/feature4286 94 7 92% ---------------------------------------- TOTAL 353 20 94%
Distributed Testing: Load
Distributed testing with dist mode set to load will report on the combined coverage of all slaves.
The slaves may be spread out over any number of hosts and each slave may be located anywhere on the
file system. Each slave will have its subprocesses measured.
Running distributed testing with dist mode set to load:
py.test --cov=myproj -n 2 tests/
Shows a terminal report:
-------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover ---------------------------------------- myproj/__init__ 2 0 100% myproj/myproj 257 13 94% myproj/feature4286 94 7 92% ---------------------------------------- TOTAL 353 20 94%
Again but spread over different hosts and different directories:
py.test --cov=myproj --dist load --tx ssh=memedough@host1//chdir=testenv1 --tx ssh=memedough@host2//chdir=/tmp/testenv2//python=/tmp/env1/bin/python --rsyncdir myproj --rsyncdir tests --rsync examples tests/
Shows a terminal report:
-------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover ---------------------------------------- myproj/__init__ 2 0 100% myproj/myproj 257 13 94% myproj/feature4286 94 7 92% ---------------------------------------- TOTAL 353 20 94%
Durations Reports: Fighting Slow Tests
Each time you switch contexts from implementation code to test code, you incur some overhead. If your tests are slow to begin with, then overhead can cause friction and frustration.
You read earlier about using marks to filter out slow tests when you run your suite. If you want to improve the speed of your tests, then it’s useful to know which tests might offer the biggest improvements. can automatically record test durations for you and report the top offenders.
Use the option to the command to include a duration report in your test results. expects an integer value and will report the slowest number of tests. The output will follow your test results:
Each test that shows up in the durations report is a good candidate to speed up because it takes an above-average amount of the total testing time.
Выбор выполняемых тестов¶
поддерживает несколько способов выбора и запуска тестов из командной строки.
Запуск тестов модуля
pytest test_mod.py
Запуск тестов из директории
pytest testing/
Запуск тестов, удовлетворяющих ключевому выражению
pytest -k "MyClass and not method"
Эта команда запустит тесты, имена которых удовлетворяют заданному строковому выражению
(без учета регистра). Строковые выражения могут включать операторы , которые
используют имена файлов, классов и функций в качестве переменных. В приведенном выше
примере будет запущен тест , но не будет запущен тест
.
Запуск тестов по идентификаторам узлов
Каждому собранному тесту присваивается уникальный идентификатор ,
который состоит из имени файла модуля, за которым следуют спецификаторы,
такие как имена классов, имена функций и параметры из параметризации, разделенные символами :
Чтобы запустить конкретный тест из модуля, выполните:
pytest test_mod.py::test_func
Еще один пример спецификации тестового метода в командной строке:
pytest test_mod.py::TestClass::test_method
Запуск маркированных тестов
pytest -m slow
Будут запущены тесты, помеченные декоратором .
Подробнее см. .
Запуск тестов из пакетов
pytest --pyargs pkg.testing
Creating JUnitXML format files¶
Создание файлов формата JUnit¶
Чтобы создать результирующие файлы в формате, понятном Jenkins
или другому серверу непрерывной интеграции, используйте вызов:
pytest --junitxml=path
Команда создает xml-файл по указанному пути.
Чтобы задать имя корневого xml-элемента для набора тестов, можно настроить параметр
в конфигурационном файле:
junit_suite_name = my_suite
Спецификация JUnit XML, по-видимому, указывает, что атрибут должен сообщать
об общем времени выполнения теста, включая выполнение setup- и teardown- методов
(, ).
Это поведение по умолчанию. Чтобы вместо этого сообщать только о длительности вызовов,
настройте параметр следующим образом:
junit_duration_report = call
record_property
Чтобы записать дополнительную информацию для теста, используйте фикстуру :
def test_function(record_property): record_property("example_key", 1) assert True
Такая запись добавит дополнительное свойство к сгенерированному тегу :
<testcase classname="test_function" file="test_function.py" line="0" name="test_function" time="0.0009"> <properties> <property name="example_key" value="1" /> </properties> </testcase>
Эту функциональность также можно использовать совместно с пользовательскими маркерами:
# content of conftest.py def pytest_collection_modifyitems(session, config, items): for item in items for marker in item.iter_markers(name="test_id"): test_id = marker.args item.user_properties.append(("test_id", test_id))
И в тесте:
# content of test_function.py import pytest @pytest.mark.test_id(1501) def test_function(): assert True
В файле получим:
<testcase classname="test_function" file="test_function.py" line="0" name="test_function" time="0.0009"> <properties> <property name="test_id" value="1501" /> </properties> </testcase>
Предупреждение
Пожалуйста, обратите внимание, что использование этой возможности приведет к записи
некорректного с точки зрения JUnitXML-схем последних версий файла и может вызывать
проблемы при работе с некоторыми серверами непрерывной интеграции
Упражнения
In Chapter 5, Plugins, on page 95, you created a plugin called pytest-nice that included a —nice command-line option. Let’s extend that to include a pytest.ini option called nice.
В главе 5 «Плагины» на стр. 95 вы создали плагин с именем который включает параметр командной строки . Давайте расширим это, включив опцию под названием .
- Добавьте следующую строку в хук-функцию :
- Места в плагине, которые используют , также должны будут вызывать . Сделайте эти изменения.
- Проверьте это вручную, добавив в файл .
- Не забудьте про тесты плагинов. Добавьте тест, чтобы убедиться, что параметр из работает корректно.
- Добавьте тесты в каталог плагинов. Вам нужно найти некоторые .
Starter Class
Your must be a subclass of where the required
information to start a process instance will be provided:
-
is waited for in the logfile before returning.
It should thus match a state of your server where it is ready to
answer queries. - is a list of arguments, used to invoke a new subprocess.
-
may be used to specify the maximum time in seconds to wait for
process startup. -
may be be used to extend the number of lines searched
for prior to considering the external process dead. By default,
the first 50 lines of stdout are redirected to a logfile, which is returned
pointing to the line right after the match. -
when provided will be called upon to check process
responsiveness after is matched. By default,
will attempt to match when
starting a process, if matched, xprocess will consider the process as ready
to answer queries. If is provided though, its return
value will also be considered to determine if the process has been
properly started. If returns True after
has been matched, will return
sucessfully. In contrast, if does not return
before timing out, will raise a exception. - Adicionally, may be defined to customize the environment in which the
new subprocess is invoked. To inherit the main test process
environment, leave set to the default ().
If the process is already running, simply the logfile is returned.
Запуск отладчика PDB (Python Debugger) при падении тестов¶
assert¶
assert ничего не делает, если выражение, которое написано после него
истинное и генерирует исключение, если выражение ложное:
In 6]: assert 5 > 1 In 7]: a = 4 In 8]: assert a in 1,2,3,4 In 9]: assert a not in 1,2,3,4 --------------------------------------------------------------------------- AssertionError Traceback (most recent call last) <ipython-input-9-1956288e2d8e> in <module> ----> 1 assert a not in 1,2,3,4 AssertionError In 10]: assert 5 < 1 --------------------------------------------------------------------------- AssertionError Traceback (most recent call last) <ipython-input-10-b224d03aab2f> in <module> ----> 1 assert 5 < 1 AssertionError
После assert и выражения можно писать сообщение. Если сообщение есть,
оно выводится в исключении:
Фикстуры: управление состоянием и зависимостями
Параметризация: комбинированные тесты
Вы видели ранее в этом руководстве, как приспособления могут использоваться для уменьшения дублирования кода путем извлечения общих зависимостей. Приспособления не так полезны, когда у вас есть несколько тестов с немного разными входами и ожидаемыми выходами. В этих случаях вы можете параметризовать одно определение теста и создаст варианты теста для вас с указанными вами параметрами.
Представьте, что вы написали функцию, чтобы определить, является ли строка палиндромом . Начальный набор тестов может выглядеть так:
Все эти тесты, кроме двух последних, имеют одинаковую форму:
Вы можете использовать, чтобы заполнить эту форму различными значениями, значительно уменьшая ваш тестовый код:
Первый аргумент – это разделенная запятыми строка имен параметров. Второй аргумент представляет собой список либо из кортежей или отдельных значений , которые представляют значение параметра (ов). Вы можете продвинуть свою параметризацию на шаг вперед, чтобы объединить все ваши тесты в один:
Несмотря на то, что это сократило ваш код, важно отметить, что в этом случае, это не очень помогло прояснить ваш тестовый код. Используйте параметризацию, чтобы отделить тестовые данные от тестового поведения, чтобы было ясно, что тест тестирует!
Specifying tests / selecting tests¶
Pytest supports several ways to run and select tests from the command-line.
Run tests in a module
pytest test_mod.py
Run tests in a directory
pytest testing/
Run tests by keyword expressions
pytest -k "MyClass and not method"
This will run tests which contain names that match the given string expression (case-insensitive),
which can include Python operators that use filenames, class names and function names as variables.
The example above will run but not .
Run tests by node ids
Each collected test is assigned a unique which consist of the module filename followed
by specifiers like class names, function names and parameters from parametrization, separated by characters.
To run a specific test within a module:
pytest test_mod.py::test_func
Another example specifying a test method in the command line:
pytest test_mod.py::TestClass::test_method
Run tests by marker expressions
pytest -m slow
Will run all tests which are decorated with the decorator.
For more information see .
Run tests from packages
pytest --pyargs pkg.testing
Поехали дальше!
Возможно вы удивитесь узнав, что вы уже написали какие то плагины, если вы проработали предыдущие главы в этой книге. Каждый раз, когда вы помещаете фикстуры и/или hook-функции в файл верхнего уровня проекта, вы создаёте локальный плагин . Это просто небольшая дополнительная работа по преобразованию этих файлов в устанавливаемые плагины, которые вы можете разделить между проектами, с другими людьми или с миром.
Мы начнем эту главу, с ответа на вопрос, где искать сторонние плагины. Довольно много плагинов доступны, так что есть приличный шанс, что кто — то уже написал изменения, которые вы хотите сделать в . Так как мы будем рассматривать плагины с открытым исходным кодом, то если плагин делает почти то, что вы хотите сделать, но не совсем, вы можете развить его, или использовать его в качестве эталона для создания собственного плагина. Хотя эта глава посвящена созданию ваших собственных плагинов, Приложение 3, плагин Sampler Pack, на странице 163 включен, чтобы дать вам почувствовать вкус того, что возможно.
В этой главе вы узнаете, как создавать плагины, и я укажу вам правильное направление для их тестирования, упаковки и распространения. Полная тема упаковки и распространения Python, слишком обширна и претендует на собственную книгу, поэтому мы не будем охватывать все. Но вы получите достаточно сведений, чтобы иметь возможность обмениваться плагинами с вашей командой. Я также расскажу о некоторых простых способах для создания плагинов с поддержкой PyPI и наименьшим количеством работы.
Bonus: BDD syntax
Conclusion
Features
-
#1556: pytest now supports files for configuration.
The configuration options is similar to the one available in other formats, but must be defined
in a table to be picked up by pytest: -
#3342: pytest now includes inline type annotations and exposes them to user programs.
Most of the user-facing API is covered, as well as internal code.If you are running a type checker such as mypy on your tests, you may start
noticing type errors indicating incorrect usage. If you run into an error that
you believe to be incorrect, please let us know in an issue.The types were developed against mypy version 0.780. Versions before 0.750
are known not to work. We recommend using the latest version. Other type
checkers may work as well, but they are not officially verified to work by
pytest yet. -
#4049: Introduced a new hook named pytest_warning_recorded to convey information about warnings captured by the internal pytest warnings plugin.
This hook is meant to replace pytest_warning_captured, which is deprecated and will be removed in a future release.
-
#6471: New command-line flags:
- `—no-header`: disables the initial header, including platform, version, and plugins.
- `—no-summary`: disables the final test summary, including warnings.
-
#6856: A warning is now shown when an unknown key is read from a config INI file.
The —strict-config flag has been added to treat these warnings as errors.
-
#6906: Added —code-highlight command line option to enable/disable code highlighting in terminal output.
-
Traditionally pytest used while changing to import test modules (which
also changes as a side-effect), which works but has a number of drawbacks, like requiring test modules
that don’t live in packages to have unique names (as they need to reside under a unique name in ).uses more fine grained import mechanisms from which don’t
require pytest to change or at all, eliminating much of the drawbacks
of the previous mode.We intend to make the default in future versions, so users are encouraged
to try the new mode and provide feedback (both positive or negative) in issue #7245. -
#7305: New configuration option allows the user to specify a list of plugins, including version information, that are required for pytest to run. An error is raised if any required plugins are not found when running pytest.
Детализация сводного отчета¶
Флаг можно использовать для отображения «краткой сводной информации по тестированию»
в конце тестового сеанса, что упрощает получение четкой картины всех сбоев, пропусков, xfails и т. д.
По умолчанию для списка сбоев и ошибок используется добавочная комбинация .
Пример:
# content of test_example.py import pytest @pytest.fixture def error_fixture(): assert def test_ok(): print("ok") def test_fail(): assert def test_error(error_fixture): pass def test_skip(): pytest.skip("skipping this test") def test_xfail(): pytest.xfail("xfailing this test") @pytest.mark.xfail(reason="always xfail") def test_xpass(): pass
$ pytest -ra =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 6 items test_example.py .FEsxX ================================== ERRORS ================================== _______________________ ERROR at setup of test_error _______________________ @pytest.fixture def error_fixture(): > assert 0 E assert 0 test_example.py:6: AssertionError ================================= FAILURES ================================= ________________________________ test_fail _________________________________ def test_fail(): > assert 0 E assert 0 test_example.py:14: AssertionError ========================= short test summary info ========================== SKIPPED $REGENDOC_TMPDIR/test_example.py:22: skipping this test XFAIL test_example.py::test_xfail reason: xfailing this test XPASS test_example.py::test_xpass always xfail ERROR test_example.py::test_error - assert 0 FAILED test_example.py::test_fail - assert 0 == 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
Параметр принимает ряд символов после себя. Использованный выше символ означает
“все, кроме успешных».
Вот полный список доступных символов, которые можно использовать:
Есть и специальные символы для пропуска отдельных групп:
Можно использовать более одного символа. Например, для того, чтобы увидеть только
упавшие и пропущенные тесты, можно выполнить:
$ pytest -rfs =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 6 items test_example.py .FEsxX ================================== ERRORS ================================== _______________________ ERROR at setup of test_error _______________________ @pytest.fixture def error_fixture(): > assert 0 E assert 0 test_example.py:6: AssertionError ================================= FAILURES ================================= ________________________________ test_fail _________________________________ def test_fail(): > assert 0 E assert 0 test_example.py:14: AssertionError ========================= short test summary info ========================== FAILED test_example.py::test_fail - assert 0 SKIPPED $REGENDOC_TMPDIR/test_example.py:22: skipping this test == 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
Использование добавляет в сводный отчет успешные тесты, а добавляет
дополнительный раздел «пройдены” (PASSED) для тестов, которые прошли, но перехватили вывод:
Breaking Changes
Предотвращение конфликтов имен файлов
Полезность наличия файла в каждом тестовом подкаталоге проекта долго меня смущали. Однако разница между тем, чтобы иметь их или не иметь, проста. Если у вас есть файлы во всех ваших тестовых подкаталогах, вы можете иметь одно и то же тестовое имя файла в нескольких каталогах. А если нет, то так сделать не получится.
Вот пример. Каталог и оба имеют файл
Неважно, что эти файлы содержат в себе, но для этого примера они выглядят так:
С такой структурой каталогов:
Эти файлы даже не имеют того же контента, но тесты испорчены. Запускать их по отдельности получится, а запустить из каталога нет:
Ни чего не понятно!
Это сообщение об ошибке не дает понять, что пошло не так.
Чтобы исправить этот тест, просто добавьте пустой файл в подкаталоги. Вот пример каталога такими же дублированными именами файлов, но с добавленными файлами :
Теперь давайте попробуем еще раз с верхнего уровня в :
Так то будет лучше.
Вы, конечно, можете убеждать себя, что у вас никогда не будет повторяющихся имен файлов, поэтому это не имеет значения. Все, типа, нормально. Но проекты растут и тестовые каталоги растут, и вы точно хотите дождаться, когда это случиться с вами, прежде чем позаботиться об этом? Я говорю, просто положите эти файлы туда. Сделайте это привычкой и не беспокойтесь об этом снова.
What about authentication?
To make requests as someone other than , pytest-drf provides the mixin. Simply create / expose a user in a fixture, and include the mixin with your view test
from django.contrib.auth.models import User from django.urls import reverse from pytest_drf import APIViewTest, AsUser, Returns200, UsesGetMethod from pytest_lambda import lambda_fixture alice = lambda_fixture( lambda User.objects.create( username='alice', first_name='Alice', last_name='Innchains', email='alice@ali.ce', )) class TestAboutMe( APIViewTest, UsesGetMethod, Returns200, AsUser('alice'), ): url = lambda_fixture(lambda reverse('about-me')) def test_it_returns_profile(self, json): expected = { 'username' 'alice', 'first_name' 'Alice', 'last_name' 'Innchains', 'email' 'alice@ali.ce', } actual = json assert expected == actual
TestRetrieve
Retrieval is simple to test: just create a beforehand, and verify the endpoint returns it in the proper structure. It’s also the first of the ViewSet actions to use the . We’re gonna define the fixture that requests
class TestRetrieve( UsesGetMethod, UsesDetailEndpoint, Returns200, ): key_value = lambda_fixture( lambda KeyValue.objects.create( key='monty', value='jython', ))
And with our expression method, verifying the API response is a cinch
def test_it_returns_key_value(self, key_value, json): expected = express_key_value(key_value) actual = json assert expected == actual
$ py.test --tb=no tests/test_kv.py::TestKeyValueViewSet::TestList::test_it_returns_200 <- pytest_drf/status.py PASSED 10% tests/test_kv.py::TestKeyValueViewSet::TestList::test_it_returns_key_values PASSED 20% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_returns_201 <- pytest_drf/status.py PASSED 30% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_creates_new_key_value PASSED 40% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_sets_expected_attrs PASSED 50% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_returns_key_value PASSED 60% tests/test_kv.py::TestKeyValueViewSet::TestRetrieve::test_it_returns_200 <- pytest_drf/status.py PASSED 70% tests/test_kv.py::TestKeyValueViewSet::TestRetrieve::test_it_returns_key_value PASSED 80% tests/test_kv.py::TestKeyValueViewSet::TestUpdate::test_it_returns_200 <- pytest_drf/status.py ERROR 90% tests/test_kv.py::TestKeyValueViewSet::TestDestroy::test_it_returns_204 <- pytest_drf/status.py ERROR 100%
Almost done!
Detailed summary report¶
The flag can be used to display a “short test summary info” at the end of the test session,
making it easy in large test suites to get a clear picture of all failures, skips, xfails, etc.
It defaults to to list failures and errors.
Example:
# content of test_example.py import pytest @pytest.fixture def error_fixture(): assert def test_ok(): print("ok") def test_fail(): assert def test_error(error_fixture): pass def test_skip(): pytest.skip("skipping this test") def test_xfail(): pytest.xfail("xfailing this test") @pytest.mark.xfail(reason="always xfail") def test_xpass(): pass
$ pytest -ra =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 6 items test_example.py .FEsxX ================================== ERRORS ================================== _______________________ ERROR at setup of test_error _______________________ @pytest.fixture def error_fixture(): > assert 0 E assert 0 test_example.py:6: AssertionError ================================= FAILURES ================================= ________________________________ test_fail _________________________________ def test_fail(): > assert 0 E assert 0 test_example.py:14: AssertionError ========================= short test summary info ========================== SKIPPED test_example.py:22: skipping this test XFAIL test_example.py::test_xfail reason: xfailing this test XPASS test_example.py::test_xpass always xfail ERROR test_example.py::test_error - assert 0 FAILED test_example.py::test_fail - assert 0 == 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
The options accepts a number of characters after it, with used
above meaning “all except passes”.
Here is the full list of available characters that can be used:
Special characters for (de)selection of groups:
More than one character can be used, so for example to only see failed and skipped tests, you can execute:
$ pytest -rfs =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 6 items test_example.py .FEsxX ================================== ERRORS ================================== _______________________ ERROR at setup of test_error _______________________ @pytest.fixture def error_fixture(): > assert 0 E assert 0 test_example.py:6: AssertionError ================================= FAILURES ================================= ________________________________ test_fail _________________________________ def test_fail(): > assert 0 E assert 0 test_example.py:14: AssertionError ========================= short test summary info ========================== FAILED test_example.py::test_fail - assert 0 SKIPPED test_example.py:22: skipping this test == 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
Using lists the passing tests, whilst adds an extra section “PASSES” with those tests that passed but had
captured output:
Warning about unraisable exceptions and unhandled thread exceptions¶
Установка плагинов
Плагины pytest устанавливаются с pip, как и другие пакеты Python. Однако,
вы можете использовать pip несколькими способами для установки плагинов.
Установка из PyPI
Поскольку PyPI является местоположением по умолчанию для pip, установка плагинов из PyPI является самым простым методом. Давайте установим плагин :
Будет установлена последняя стабильная версия от PyPI.
Установка из файла .tar.gz или .whl
Пакеты на PyPI распространяются как zip-файлы с расширениями и/или . Они часто упоминаются как «tar balls» и «wheels». Если у вас возникли проблемы с попыткой работать с PyPI напрямую (что может случиться с брандмауэрами и другими сетевыми осложнениями), вы можете загрузить либо , либо и установить из этого-того.
Вам не нужно распаковывать или танцевать с бубном; просто укажите pip на него:
Вы можете иметь заначку плагинов (и других пакетов Python) в локальном или общем каталоге в формате или и использовать это вместо PyPI для установки плагинов:
указывает не подключаться к PyPI. указывает pip искать в каталоге . Этот метод особенно полезен, если у вас есть как сторонние, так и собственные плагины, хранящиеся локально, а также если вы создаете новые виртуальные среды для непрерывной интеграции или с tox. (Мы поговорим как о tox, так и о непрерывной интеграции в главе 7, используя pytest с другими инструментами, на странице 125.)
Обратите внимание, что с помощью метода установки локального каталога вы можете установить несколько версий и указать, какую версию вы хотите, добавив == и номер версии:
Установка из репозитория Git
Вы можете установить плагины непосредственно из Git-репозитория в этом случае GitHub:
Можно также указать тег версии:
Или можно указать ветвь:
Установка из репозитория Git особенно полезна, если вы храните свою собственную работу в Git или если требуемая версия плагина или плагин отсутствует в PyPI.
Вызов pytest из кода Python¶
Parametrization: Combining Tests
You saw earlier in this tutorial how fixtures can be used to reduce code duplication by extracting common dependencies. Fixtures aren’t quite as useful when you have several tests with slightly different inputs and expected outputs. In these cases, you can parametrize a single test definition, and will create variants of the test for you with the parameters you specify.
Imagine you’ve written a function to tell if a string is a palindrome. An initial set of tests could look like this:
All of these tests except the last two have the same shape:
You can use to fill in this shape with different values, reducing your test code significantly:
The first argument to is a comma-delimited string of parameter names. The second argument is a list of either tuples or single values that represent the parameter value(s). You could take your parametrization a step further to combine all your tests into one:
Calling pytest from Python code¶
Putting it all together
Что такое pytest?
Dropping to PDB (Python Debugger) on failures¶
Python comes with a builtin Python debugger called PDB.
allows one to drop into the PDB prompt via a command line option:
pytest --pdb
This will invoke the Python debugger on every failure (or KeyboardInterrupt).
Often you might only want to do this for the first failing test to understand
a certain failure situation:
pytest -x --pdb # drop to PDB on first failure, then end test session pytest --pdb --maxfail=3 # drop to PDB for first three failures
Note that on any failure the exception information is stored on
, and . In
interactive use, this allows one to drop into postmortem debugging with
any debug tool. One can also manually access the exception information,
for example: