Python testing с pytest. плагины, глава 5

Создание устанавливаемого плагина

Процесс обмена плагинами с другими пользователями четко определен. Даже если вы никогда не включите свой собственный плагин в 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 вы создали плагин с именем который включает параметр командной строки . Давайте расширим это, включив опцию под названием .

  1. Добавьте следующую строку в хук-функцию :
  2. Места в плагине, которые используют , также должны будут вызывать . Сделайте эти изменения.
  3. Проверьте это вручную, добавив в файл .
  4. Не забудьте про тесты плагинов. Добавьте тест, чтобы убедиться, что параметр из работает корректно.
  5. Добавьте тесты в каталог плагинов. Вам нужно найти некоторые .

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:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector