Как правило, фреймворк pytest
вызывается с помощью команды pytest
(другие способы вызова смотрите ниже). Это выполнит все тесты во всех файлах, имена которых следуют форме test_*.py
или *_test.py
в текущем каталоге и его подкаталогах. В более общем плане pytest
следует стандартным правилам обнаружения тестов.
Получение справки по версии, именам опций, переменным средам:
pytest --version
: показывает, откуда был импортирован pytest
;pytest --fixtures
: показать доступные аргументы встроенной функции;pytest -h | --help
: показать справку по параметрам командной строки и конфигурационного файла.Внимание! Тесты будут отлично запускаться, если проект расположен внутри установленной виртуальной среды.
Если не можете понять, почему постоянно лезет ошибка импорта, то посмотрите материал "Интеграция с проектом тестов
pytest
".
pytest
из кода Python;Фреймворк 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
.
По умолчанию 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")
Теперь:
setup.py
будет пропущен;Также можно игнорировать файлы, применяя подстановочные знаки в стиле 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 падений, используются параметры:
# остановка после первого упавшего теста $ pytest -x # остановка после первых трех упавших тестов $ pytest --maxfail=3