Сообщить об ошибке.

Как запускать/вызывать тесты pytest в Python

Как правило, фреймворк pytest вызывается с помощью команды pytest (другие способы вызова смотрите ниже). Это выполнит все тесты во всех файлах, имена которых следуют форме test_*.py или *_test.py в текущем каталоге и его подкаталогах. В более общем плане pytest следует стандартным правилам обнаружения тестов.

Получение справки по версии, именам опций, переменным средам:

  • pytest --version: показывает, откуда был импортирован pytest;
  • pytest --fixtures: показать доступные аргументы встроенной функции;
  • pytest -h | --help: показать справку по параметрам командной строки и конфигурационного файла.

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

Если не можете понять, почему постоянно лезет ошибка импорта, то посмотрите материал "Интеграция с проектом тестов pytest".

Содержание:


Выборочный запуск тестов.

Фреймворк pytest поддерживает несколько способов запуска и выбора тестов из командной строки.

Запуск всех тестов, расположенных в модуле.

$ pytest test_mod.py

Запуск всех тестов, расположенных в каталоге.

$ pytest testing/

Запуск тестов из пакетов.

$ pytest --pyargs pkg.testing

Команда позволит импортировать pkg.testing и использовать его расположение в файловой системе для поиска и запуска тестов.

Запуск тестов по идентификаторам узлов.

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

Чтобы запустить конкретный тест в модуле из командной строки используя уникальный идентификатор узла:

$ pytest test.py::test_func

Другой пример указания метода тестирования из командной строки:

$ pytest test.py::TestClass::test_method

Примечание: Чтобы посмотреть все сгенерированные ID-строки, необходимо запустить команду $ pytest --collect-only testing/, где testing - это директория с тестами.

Запуск тестов, которые завершились с ошибкой.

Создадим 50 тестовых вызовов, из которых только 2 потерпят неудачу:

# test_50.py
import pytest

@pytest.mark.parametrize("i", range(50))
def test_num(i):
    if i in (17, 25):
        pytest.fail("ПРОВАЛ!")

Если запустить тест в первый раз $ pytest -q test_50.py, то увидим две ошибки. Если затем запустите его с опциями --lf: $ pytest --lf test_50.py, то будут выполнены только два неуспешных теста из последнего запуска, а 48 пройденных тестов будут отменены.

Теперь, если тест запустить с опциями --ff, то будут запущены все тесты, но сначала будут выполнены первые предыдущие сбои:

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

Запуск тестов отмеченных пользовательскими маркерами.

Чтобы запустить набор тестов с определенными маркерами, сначала их надо зарегистрировать. Это делается очень просто в файле pytest.ini, который расположен в корне проекта:

# pytest.ini
[pytest]
markers =
    slow: медленный тест.
    fast: быстрый тест.

Внимание! Не используйте в названиях маркеров ключевые слова языка Python.

Незарегистрированные метки всегда будут выдавать предупреждение. Чтобы убрать предупреждения, необходимо зарегистрировать их программно, используя хук pytest_configure().

def pytest_configure(config):
    # регистрируется метка `@pytest.mark.fast`
    config.addinivalue_line("markers", "fast: маркер зарегистрирован программно.")
    # регистрируется метка `@pytest.mark.slow`
    config.addinivalue_line("markers", "slow: медленный тест.")

Теперь необходимо "пометить" нужные тестовые функции зарегистрированными маркерами/метками. Это делается следующим образом:

import pytest

def test_send_http():
    pass  

@pytest.mark.slow
def test_something_quick():
    pass

@pytest.mark.slow
def test_another():
    pass

@pytest.mark.fast
class TestClass:
    def test_method(self):
        pass

Затем, что бы выполнит все тесты помеченные маркером/декоратором @pytest.mark.slow, необходимо выполнить команду:

$ pytest -m slow

Или наоборот, запустить все тесты, кроме помеченных декоратором @pytest.mark.slow:

$ pytest -m "not slow"

Для выбора тестов основе маркеров можно использовать операторы or, not. Не используйте в названиях маркеров ключевые слова языка Python. К примеру, запуск с маркерами "break" или "pass" приведет к синтаксической ошибке, так как опция -m вычисляется при помощи функции eval().

Что бы выполнит тесты помеченные @pytest.mark.slow и @pytest.mark.fast, необходимо выполнить команду:

$ pytest -m "slow or fast"

Декоратор @pytest.mark можно применять для классов, чтобы выполнить все его тестовые методы:

import pytest

@pytest.mark.slow
class TestClass:
    def test_startup(self):
        pass

    def test_startup_and_more(self):
        pass

Можно установить атрибут pytestmark тестовому классу TestClass:

import pytest

class TestClass:
    pytestmark = pytest.mark.slow
    # или список маркеров:
    pytestmark = [pytest.mark.slow, pytest.mark.fast]
    ...

Можно также установить маркер на уровне модуля:

import pytest 

pytestmark = pytest.mark.slow
# или список маркеров:
pytestmark = [pytest.mark.slow, pytest.mark.fast]
...

В случаях со списком маркеров, они будут применяться (очередность выполнения слева направо) ко всем тестам, расположенным в классе (первый случай) или в файле модуля (второй случай).

Это еще не все, маркировать для выполнения можно даже один параметр в тесте!

# test.py
import pytest

@pytest.mark.bar
@pytest.mark.parametrize(
    "test_input, expected",
    [("3+5", 8), ("2+4", 6), 
    pytest.param("6*9", 42, marks=pytest.mark.foo)],
)
def test_eval(test_input, expected):
    assert eval(test_input) == expected

При запуске из командной строки $ pytest -m bar test.py, тест test_eval() выполнится со всеми тремя параметрами, а при запуске $ pytest -m foo test.py только с одним, помеченным как pytest.mark.foo.

Запуск тестов на основе совпадения подстрок в именах тестов.

Что бы запустить группу тестов на основе совпадения подстроки в имени функции/метода теста, необходимо использовать опцию CLI -k <expr>. Эта опция реализует совпадение подстрок в именах тестов, записанных в выражении <expr>. Такое поведение упрощает выбор, используя имена тестовых функций/методов:

Изменено в версии 5.4: Сопоставление выражений теперь нечувствительно к регистру.

# запустит все тесты, если в именах функций, 
# методов или классов встречается подстрока `http`
$ pytest -v -k http

Можно также запустить все тесты, кроме тех, которые соответствуют ключевому слову :

# запустит все тесты, если в именах функций, методов
# или классов НЕ встречается подстрока `send_http`
$ pytest -v -k "not send_http"

Или выбрать "http" и "quick" тесты:

# запустит все тесты, если в именах функций, методов
# или классов НЕ встречается подстрока `send_http`
$ pytest -k "http or quick" -v

Для выбора тестов основе подстрок можно использовать операторы and, or, not и круглые скобки (смотрите логические выражения в Python).

Пример запуска теста TestMyClass.test_something, но не TestMyClass.test_method_simple.

$ pytest -k "MyClass and not method"

Важно! Используемые подстрок в выражении <expr> не должны содержать ключевые слова языка Python. К примеру, запуск с переданными подстроками "break" или "from" приведет к синтаксической ошибке, так как опция -k вычисляет выражения <expr> при помощи функции eval().

Обратите внимание! Что указанные в выражении <expr> подстроки будут искаться не только в именах тестовых функций/методов, но и именах тестовых файлов, тестовых классов, во всех маркерах, в том числе присвоенных атрибутам классов и соответственно найденные тесты будут собираться для последующего выполнения. Так что с умом подходите к выбору названий файлов, классов, функций и методов, используемых в тестировании.

Игнорирование путей к определенным тестам.

Можно легко игнорировать определенные тестовые каталоги и модули/файлы во время сбора тестов, передав опцию командной строки --ignore=path. Фреймворк pytest позволяет указывать несколько опций --ignore. Пример:

tests/
|-- example
|   |-- test_example_01.py
|   |-- test_example_02.py
|   '-- test_example_03.py
|-- foobar
|   |-- test_foobar_01.py
|   |-- test_foobar_02.py
|   '-- test_foobar_03.py
'-- hello
    '-- world
        |-- test_world_01.py
        |-- test_world_02.py
        '-- test_world_03.py

Теперь, если вызвать:

$ pytest --ignore=tests/foobar/test_foobar_03.py --ignore=tests/hello/

то увидим, что pytest собирает только те тестовые модули, которые не соответствуют указанным шаблонам.

Посмотреть то, что собирает фреймворк pytest не запуская сами тесты можно командой:

$ pytest --collect-only pythoncollection.py

Параметр CLI --ignore-glob позволяет игнорировать пути к тестовым файлам на основе подстановочных знаков в стиле оболочки Unix. Если необходимо исключить тестовые модули, которые заканчиваются на _01.py, выполните pytest с параметром --ignore-glob='*_01.py'.

Запрет сбора некоторых тестов.

Тесты можно отменить по отдельности во время их сбора, передав параметр --deselect=item. Например, test/foobar/test_foobar_01.py содержит test_a и test_b. Можно запустить все тесты в папке tests/, за исключением tests/foobar/test_foobar_01.py::test_a, вызвав:

$ pytest --deselect tests/foobar/test_foobar_01.py::test_a

Фреймворк pytest допускает указание в CLI подряд несколько опций --deselect.

Добавление опций командной строки "по умолчанию".

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

# pytest.ini
[pytest]
addopts = -ra -q

Кроме того, можно установить опции CLI, которые будут добавляться "по умолчанию" в переменную среды PYTEST_ADDOPTS:

export PYTEST_ADDOPTS="-v"

Вот так строится командная строка при наличии дополнений или переменной окружения:

<pytest.ini:addopts> $PYTEST_ADDOPTS <extra command-line arguments>

Итак, если пользователь выполняет в командной строке:

$ pytest -m slow

То фактическая выполняемая командная строка будет выглядеть:

$ pytest -ra -q -v -m slow

Обратите внимание, что в случае конфликтующих опций побеждает последняя, ​​поэтому в приведенном выше примере будет показан подробный вывод, потому что -v перезаписывает -q.

Использование одинаковых путей/каталогов в CLI.

По умолчанию pytest игнорирует повторяющиеся пути, указанные в командной строке. Пример:

# Тесты в `path_a` будут собраны только один раз.
$ pytest path_a path_a

Чтобы собрать тесты дважды, нужно использовать опцию CLI --keep-duplicates. Пример:

$ pytest --keep-duplicates path_a path_a

Так как сборщик тестов работает только с каталогами, то если дважды указать один и тот же файл Python, то pytest включит его дважды, независимо от того, указан ли параметр --keep-duplicates. Пример:

$ pytest test_a.py test_a.py

Управление рекурсивным обходом при сборе тестов.

Можно установить параметр norecursedirs в ini-файле, например, в pytest.ini, расположенным в корневом каталоге проекта:

# pytest.ini
[pytest]
norecursedirs = .svn _build tmp*

Такая настройка укажет pytest не проводить рекурсивный поиск тестов в каталогах subversion или sphinx-build или в любом каталоге с префиксом tmp.

Изменение соглашений об именовании тестов.

Можно настроить различные соглашения об именах тестов, установив python_files, python_classes и python_functions в файле конфигурации. Например:

# pytest.ini
# `pytest` будет искать "check" вместо "test"
[pytest]
python_files = check_*.py
python_classes = Check
python_functions = *_check

Эта настройка укажет pytest искать тесты в файлах, соответствующих шаблону glob check_* .py, проверять в классах префиксы Check, а также искать функции и методы по шаблону *_check. Например:

# check_myapp.py
class CheckMyApp:
    def simple_check(self):
        pass

    def complex_check(self):
        pass

В файле конфигурации можно указывать несколько шаблонов, добавив между ними пробел:

[pytest]
python_files = test_*.py example_*.py

Примечание: настройки python_functions и python_classes не влияют на обнаружение тестов unittest.TestCase, так как их обнаружение производится средствами unittest.

Настройка собственного набора тестов.

Можно легко указать pytest искать тесты в каждом файле Python:

# pytest.ini
[pytest]
python_files = *.py

При этом, многие проекты имеют файл setup.py, который импортировать не надо. Более того, могут быть файлы, импортируемые только определенной версией Python.

Допустим есть такой файл модуля:

# pkg/module_py2.py
def test_only_on_python2():
    try:
        assert 0
    except Exception, e:
        pass

и макет файла setup.py, типа такого:

# of setup.py
# вызовет исключение, если будет импортирован
0 / 0

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

# conftest.py
import sys

collect_ignore = ["setup.py"]
if sys.version_info[0] > 2:
    collect_ignore.append("pkg/module_py2.py")

Теперь:

  • Если запустить интерпретатор Python2, то выполниться тест, но файл setup.py будет пропущен;
  • Если запустить интерпретатор Python3, то оба файл будут опущены.

Также можно игнорировать файлы, применяя подстановочные знаки в стиле Unix, добавив шаблоны в collect_ignore_glob.

В следующем примере conftest.py игнорирует файл setup.py и, кроме того, все файлы, заканчивающиеся на *_py2.py, при использовании Python3:

# content of conftest.py
import sys

collect_ignore = ["setup.py"]
if sys.version_info[0] > 2:
    collect_ignore_glob = ["*_py2.py"]

Начиная с версии PyTest 2.6, пользователи могут запретить pytest собирать/находить классы, начинающиеся с Test, установив для атрибута __test__ значение False.

# Не будет обнаружено в качестве теста
class TestClass:
    __test__ = False

Управление загрузкой плагинов из командной строки.

Ранняя загрузка плагинов.

Можно заранее загружать плагины (внутренние и внешние) явно в командной строке с параметром -p:

$ pytest -p mypluginmodule

Опция получает параметр name, который может быть:

  • Полным именем модуля через точку, например, myproject.plugins. Имя с точками должно быть импортируемым.
  • Имя точки входа плагина. Это имя передается setuptools при регистрации плагина. Например, для ранней загрузки плагина pytest-cov можно использовать: pytest -p pytest_cov

Отключение плагинов.

Чтобы отключить загрузку определенных подключаемых модулей во время вызова, используйте параметр -p вместе с префиксом no:.

Пример: чтобы отключить загрузку плагина doctest, который отвечает за выполнение тестов doctest из текстовых файлов, вызовите pytest следующим образом:

$ pytest -p no:doctest

Вызов pytest из кода Python.

Можно вызвать pytest напрямую из кода Python:

retcode = pytest.main()

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

retcode = pytest.main(["-x", "mytestdir"])

Можно указывать дополнительные плагины для pytest.main:

# содержание `myinvoke.py`
import pytest
import sys

class MyPlugin:
    def pytest_sessionfinish(self):
        print("*** отчет о завершении тестового запуска")

if __name__ == "__main__":
    sys.exit(pytest.main(["-qq"], plugins=[MyPlugin()]))

Запустив его, можно увидеть, что MyPlugin был добавлен и его хук был вызван:

$ python myinvoke.py
*** отчет о завершении тестового запуска

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

Продолжительность выполнения теста.

Изменено в версии PyTest 6.0.

Чтобы получить список 10 самых медленных тестов продолжительностью более 1,0 с:

$ pytest --durations=10 --durations-min=1.0

По умолчанию pytest не будет отображать слишком маленькую продолжительность теста (0,005 с), если в командной строке не будет передан параметр -vv.

Остановка тестирования после первых N падений.

Чтобы остановить процесс тестирования после первых N падений, используются параметры:

# остановка после первого упавшего теста
$ pytest  -x
# остановка после первых трех упавших тестов
$ pytest --maxfail=3