[Перевод] Python Testing с pytest. Конфигурация, ГЛАВА 6
Вернуться Дальше
В этой главе мы рассмотрим файлы конфигурации, которые влияют на pytest, обсудим, как pytest изменяет свое поведение на их основе, и внесем некоторые изменения в файлы конфигурации проекта Tasks.
6 и pytest 3. Примеры в этой книге написаны с использованием Python 3. pytest 3. 2. 6, 2. 2 поддерживает Python 2. 3+. 7 и Python 3.
Вам не нужно загружать исходный код, чтобы понять тестовый код; тестовый код представлен в удобной форме в примерах. Исходный код для проекта Tasks, а также для всех тестов, показанных в этой книге, доступен по ссылке на веб-странице книги в pragprog.com. Там же, на веб-странице книги есть ссылка для сообщений errata и дискуссионный форум. Но что бы следовать вместе с задачами проекта, или адаптировать примеры тестирования для проверки своего собственного проекта (руки у вас развязаны!), вы должны перейти на веб-страницу книги и скачать работу.
Под спойлером приведен список статей этой серии.
Оглавление
Конфигурация
В этой главе мы рассмотрим файлы конфигурации, которые влияют на pytest, обсудим, как pytest изменяет свое поведение на их основе, и внесем некоторые изменения в файлы конфигурации проекта Tasks. До сих пор в этой книге я говорил о различных нетестовых файлах, которые влияют на pytest в основном мимоходом, за исключением conftest.py, который я довольно подробно рассмотрел в главе 5, Плагины, на странице 95.
Понимание файлов конфигурации pytest
Прежде чем я расскажу, как вы можете изменить поведение по умолчанию в pytest, давайте пробежимся по всем не тестовым файлам в pytest и, в частности, кто должен заботиться о них.
Следует знать следующее:
- pytest.ini: Это основной файл конфигурации Pytest, который позволяет вам изменить поведение по умолчанию. Поскольку вы можете внести довольно много изменений в конфигурацию, большая часть этой главы посвящена настройкам, которые вы можете сделать в
pytest.ini
. - conftest.py: Это локальный плагин, позволяющий подключать хук-функции и фикстуры для каталога, в котором существует файл
conftest.py
, и всех его подкаталогов. Файлconftest.py
описан в главе 5 «Плагины» на стр. 95. __init__.py
: При помещении в каждый test-подкаталог этот файл позволяет вам иметь идентичные имена test-файлов в нескольких каталогах test. Мы рассмотрим пример того, что пойдет не так без файлов__init__.py
в тестовых каталогах в статье «Избегание коллизий имен файлов» на стр. 120.
Если вы используете tox, вас заинтересует:
- tox.ini: Этот файл похож на
pytest.ini
, но дляtox
. Однако вы можете разместить здесь свою конфигурациюpytest
вместо того, чтобы иметь и файлtox.ini
, и файлpytest.ini
, сохраняя вам один файл конфигурации. Tox рассматривается в главе 7, "Использование pytest с другими инструментами", на стр. 125.
Если вы хотите распространять пакет Python (например, Tasks), этот файл будет интересен:
- setup.cfg: Это также файл в формате INI, который влияет на поведение файла
setup.py
. Можно добавить несколько строк вsetup.py
для запускаpython setup.py test
и запустить все ваши тесты pytest. Если вы распространяете пакет, возможно, у вас уже есть файлsetup.cfg
, и вы можете использовать этот файл для хранения конфигурации Pytest. Вы увидите, как это делается в Приложении 4, «Упаковка и распространение проектов Python», на стр. 175.
Независимо от того, в какой файл вы поместили конфигурацию pytest, формат будет в основном одинаковым.
Для pytest.ini
:
ch6/format/pytest.ini
[pytest]
addopts = -rsxX -l --tb=short --strict
xfail_strict = true
... more options ...
Для tox.ini
:
ch6/format/tox.ini
... tox specific stuff ...
[pytest]
addopts = -rsxX -l --tb=short --strict
xfail_strict = true
... more options ...
Для setup.cfg
:
ch6/format/setup.cfg
... packaging specific stuff ...
[tool:pytest]
addopts = -rsxX -l --tb=short --strict
xfail_strict = true
... more options ...
Единственное отличие состоит в том, что заголовок раздела для setup.cfg — это [tool:pytest]
вместо [pytest]
.
List the Valid ini-file Options with pytest –help
Вы можете получить список всех допустимых параметров для pytest.ini
из pytest --help
:
$ pytest --help
...
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found: markers (linelist) markers for test functions empty_parameter_set_mark (string) default marker for empty parametersets norecursedirs (args) directory patterns to avoid for recursion testpaths (args) directories to search for tests when no files or directories are given in the command line. console_output_style (string) console output: classic or with additional progress information (classic|progress). usefixtures (args) list of default fixtures to be used with this project python_files (args) glob-style file patterns for Python test module discovery python_classes (args) prefixes or glob names for Python test class discovery python_functions (args) prefixes or glob names for Python test function and method discovery xfail_strict (bool) default for the strict parameter of xfail markers when not given explicitly (default: False) junit_suite_name (string) Test suite name for JUnit report junit_logging (string) Write captured log messages to JUnit report: one of no|system-out|system-err doctest_optionflags (args) option flags for doctests doctest_encoding (string) encoding used for doctest files cache_dir (string) cache directory path. filterwarnings (linelist) Each line specifies a pattern for warnings.filterwarnings. Processed after -W and --pythonwarnings. log_print (bool) default value for --no-print-logs log_level (string) default value for --log-level log_format (string) default value for --log-format log_date_format (string) default value for --log-date-format log_cli (bool) enable log display during test run (also known as "live logging"). log_cli_level (string) default value for --log-cli-level log_cli_format (string) default value for --log-cli-format log_cli_date_format (string) default value for --log-cli-date-format log_file (string) default value for --log-file log_file_level (string) default value for --log-file-level log_file_format (string) default value for --log-file-format log_file_date_format (string) default value for --log-file-date-format addopts (args) extra command line options minversion (string) minimally required pytest version xvfb_width (string) Width of the Xvfb display xvfb_height (string) Height of the Xvfb display xvfb_colordepth (string) Color depth of the Xvfb display xvfb_args (args) Additional arguments for Xvfb xvfb_xauth (bool) Generate an Xauthority token for Xvfb. Needs xauth. ...
Вы увидите все эти настройки в этой главе, за исключением doctest_optionflags
, который рассматривается в главе 7, "Использование pytest с другими инструментами", на странице 125.
Плагины могут добавлять опции ini-файлов
Для плагинов (и файлов conftest.py) возможно добавить опции файла ini. Предыдущий список настроек не является константой. Добавленные опции также будут добавлены в вывод команды pytest --help.
Теперь давайте рассмотрим некоторые изменения конфигурации, которые мы можем внести с помощью встроенных настроек INI-файла, доступных в core pytest.
Изменение параметров командной строки по умолчанию
Вы можете обнаружить, что всегда используете некоторые из этих options—or
и предпочитаете использовать them—for a project
. Вы использовали уже некоторые параметры командной строки для pytest, таких как -v/--verbose
для подробного вывода -l/--showlocals
для просмотра локальных переменных с трассировкой стека для неудачных тестов. Вот набор, который мне нравится: Если вы устанавливаете addopts
в pytest.ini
для нужных вам параметров, то вам больше не придется вводить их.
[pytest]
addopts = -rsxX -l --tb=short --strict
Ключ -l
позволит pytest вывести трассировку стека для локальных переменных в случае каждого сбоя. Ключ -rsxX
дает установку pytest сообщать о причинах всех skipped
, xfailed
или xpassed
тестов. Однако, оставит файл и номер строки. --tb=short
удалит большую часть трассировки стека. Вы увидите, как это сделать в следующем разделе. Параметр --strict
запрещает использование маркеров, если они не зарегистрированы в файле конфигурации.
Регистрация маркеров, чтобы избежать опечаток маркера
Тем не менее, слишком легко ошибиться в маркере и в конечном итоге некоторые тесты помечены @pytest.mark.smoke
, а некоторые отмечены @pytest.mark.somke
. Пользовательские маркеры, как описано в разделе «Маркировка тестовых функций» на странице 31, отлично подходят для того, чтобы позволить вам пометить подмножество тестов для запуска определенным маркером. pytest просто думает, что вы создали два маркера. По умолчанию это не ошибка. Однако это можно исправить, зарегистрировав маркеры в pytest.ini, например так:
[pytest]
...
markers = smoke: Run the smoke test test functions get: Run the test functions that test tasks.get()
...
Зарегистрировав эти маркеры, вы теперь также можете увидеть их с помощью pytest --markers
с их описаниями:
$ cd /path/to/code/ch6/b/tasks_proj/tests
$ pytest --markers @pytest.mark.smoke: Run the smoke test test functions @pytest.mark.get: Run the test functions that test tasks.get() @pytest.mark.skip(reason=None): skip the ... ...
Когда они зарегистрированы, они отображаются в списке, и если вы используете --strict
, любые маркеры с ошибками или незарегистрированные отображаются как ошибки. Если маркеры не зарегистрированы, они не будут отображаться в списке --markers
. В ch6/a
пусто. Единственная разница между ch6/a/tasks_proj
и ch6/b/tasks_proj
заключается в содержимом файла pytest.ini. Давайте попробуем запустить тесты без регистрации каких-либо маркеров:
$ cd /path/to/code/ch6/a/tasks_proj/tests
$ pytest --strict --tb=line ============================= test session starts ============================= collected 45 items / 2 errors =================================== ERRORS ====================================
______________________ ERROR collecting func/test_add.py ______________________ 'smoke' not a registered marker
________________ ERROR collecting func/test_api_exceptions.py _________________ 'smoke' not a registered marker
!!!!!!!!!!!!!!!!!!! Interrupted: 2 errors during collection !!!!!!!!!!!!!!!!!!!
=========================== 2 error in 1.10 seconds ===========================
You’ll thank me later. If you use markers in pytest.ini
to register your markers, you may as well add --strict
to your addopts
while you’re at it. Let’s go ahead and add a pytest.ini file to the tasks project:
Ты поблагодаришь меня позже. Если вы используете маркеры в pytest.ini
для регистрации своих маркеров, вы также можете добавить --strict
к своим addopts
. Давайте продолжим и добавим файл pytest.ini в проект задач:
Круто?! Если вы используете маркеры в pytest.ini
для регистрации маркеров, вы можете также добавить --strict
к имеющимся при помощи addopts
. Отложим благодарности и добавим файл pytest.ini
в проект tasks
:
ch6/b/tasks_proj/tests/pytest.ini
[pytest]
addopts = -rsxX -l --tb=short --strict
markers = smoke: Run the smoke test test functions get: Run the test functions that test tasks.get()
Здесь комбинация флагов предпочитаемые по умолчанию:
-rsxX
, чтобы сообщить, какие тесты skipped, xfailed, или xpassed,--tb = short
для более короткой трассировки при сбоях,--strict
что бы разрешить только объявленные маркеры.
И список маркеров для проекта.
Это должно позволить нам проводить тесты, в том числе дымовые(smoke tests):
$ cd /path/to/code/ch6/b/tasks_proj/tests
$ pytest --strict -m smoke ===================== test session starts ======================
collected 57 items func/test_add.py .
func/test_api_exceptions.py .. ===================== 54 tests deselected ======================
=========== 3 passed, 54 deselected in 0.06 seconds ============
Требование минимальной версии Pytest
Например, я задумал использовать approx()
при тестировании чисел с плавающей запятой для определения “достаточно близкого” равенства в тестах. Параметр minversion
позволяет указать минимальную версию pytest, ожидаемую для тестов. 0. Но эта функция не была введена в pytest до версии 3. Чтобы избежать путаницы, я добавляю следующее в проекты, которые используют approx()
:
[pytest]
minversion = 3.0
Таким образом, если кто-то пытается запустить тесты, используя более старую версию pytest, появится сообщение об ошибке.
Остановка pytest от поиска в неправильных местах
Ну, нет. Знаете ли вы, что одно из определений «recurse» заключается в том, что бы дважды выругаться в собственом коде? pytest включит обнаружение тестов рекурсивно исследуя кучу каталогов. На самом деле, это означает учет подкаталогов. Но есть некоторые каталоги, которые вы хотите исключить из просмотра pytest.
* Build dist CVS _darcs and *.egg. Значением по умолчанию для norecurse
является '. Having '.*'
— это хорошая причина назвать вашу виртуальную среду '.venv', потому что все каталоги, начинающиеся с точки, не будут видны.
В случае проекта Tasks, не помешает указать src
, потому что поиск в тестовых файлах с помощью pytest будет пустой тратой времени.
[pytest]
norecursedirs = .* venv src *.egg dist build
При переопределении параметра, который уже имеет полезное значение, такого как этот параметр, полезно знать, какие есть значения по умолчанию, и вернуть те, которые вам нужны, как я делал в предыдущем коде с *.egg dist build
.norecursedirs
— своего рода следствие для тестовых путей, поэтому давайте посмотрим на это позже.
спецификация дерева тестового каталога
testspaths
— это список каталогов относительно корневого каталога для поиска тестов. В то время как norecursedirs
указывает pytest куда не надо заглядыывать, testpaths
говорит pytest, где искать. Он используется только в том случае, если в качестве аргумента не указан каталог, файл или nodeid
.
Предположим, что для проекта Tasks
мы поместили pytest.ini
в каталог tasks_proj
вместо тестов:
\code\tasks_proj>tree/f
.
│ pytest.ini
│
├───src
│ └───tasks
│ api.py
│ ...
│
└───tests │ conftest.py │ pytest.ini │ ├───func │ test_add.py │ ... │ ├───unit │ test_task.py │ __init__.py │ ...
Тогда может иметь смысл поместить тесты в testpaths
:
[pytest]
testpaths = tests
Проблема здесь в том, что во время разработки и отладки тестов я часто перебираю тестовый каталог, поэтому я могу легко тестировать подкаталог или файл, не указывая весь путь. Теперь, если вы запускаете pytest из каталога tasks_proj
, pytest будет искать только в tasks_proj/tests
. Поэтому мне этот параметр мало помогает в интерактивном тестировании.
В этих случаях вы знаете, что корневой каталог будет фиксированным, и вы можете перечислить каталоги относительно этого фиксированного корневого каталога. Тем не менее, он отлично подходит для тестов, запускаемых с сервера непрерывной интеграции или с tox-а. Это также те случаи, когда вы действительно хотите сократить время тестирования, так что избавиться от поиска тестов — это здорово.
Однако, как вы уже видели, тестовые пути мало помогают в интерактивном тестировании из разных частей файловой системы. На первый взгляд может показаться глупым использовать одновременно и тестовые пути, и norecursedirs
. Кроме того, если у вас есть каталоги с тестами, которые не содержат тестов, вы можете использовать norecursedirs
, чтобы избежать их. В этих случаях norecursedirs
могут помочь. Но на самом деле, какой смысл ставить дополнительные каталоги в тесты, которые не имеют тестов?
Изменение Правил Обнаружения Тестов
Стандартные правила обнаружения тестов: pytest находит тесты для запуска на основе определенных правил обнаружения тестов.
Вы можете указать имена файлов или каталогов в командной строке. • Начните с одного или нескольких каталогов. Ищите методы в тех классах, которые начинаются с `test, но не имеют метода
init`. Если вы ничего не указали, используется текущий каталог.
• Искать в каталоге и во всех его подкаталогах тестовые модули.
• Тестовый модуль — это файл с именем, похожим на test_*.py
или *_test.py
.
• Посмотрите в тестовых модулях функции, которые начинаются с test.
• Ищите классы, которые начинаются с Test.
Это стандартные правила обнаружения; Однако вы можете изменить их.
python_classes
Класс также не может иметь метод __init__()
. Обычное правило обнаружения тестов для pytest и классов — считать класс потенциальным тестовым классом, если он начинается с Test*
. Вот где приходит python_classes
: Но что, если мы захотим назвать наши тестовые классы как <something>Test
или <something>Suite
?
[pytest]
python_classes = *Test Test* *Suite
Это позволяет нам называть классы так:
class DeleteSuite(): def test_delete_1(): ... def test_delete_2(): ... ....
python_files
Кажется разумным. Как и pytest_classes
, python_files
изменяет правило обнаружения тестов по умолчанию, которое заключается в поиске файлов, начинающихся с test_*
или имеющих в конце *_test
.
Допустим, у вас есть пользовательский тестовый фреймворк, в котором вы назвали все свои тестовые файлы check_<something>.py
. Вместо того, чтобы переименовывать все ваши файлы, просто добавьте строку в pytest.ini
следующим образом:
[pytest]
python_files = test_* *_test check_*
Теперь вы можете постепенно перенести соглашение об именах, если хотите, или просто оставить его как check_*
. Очень просто.
python_functions
Значение по умолчанию — test_*
. python_functions
действует как две предыдущие настройки, но для тестовых функций и имен методов. А чтобы добавить check_*
—вы угадали—сделайте это:
[pytest]
python_functions = test_* check_*
Так что, если вам не нравится соглашение об именах по умолчанию, просто измените его. Соглашения об именах pytest
не кажутся такими уж ограничивающими, не так ли? Миграция сотен тестовых файлов — определенно веская причина. Тем не менее, я призываю вас иметь более вескую причину для таких решений.
Запрет XPASS
Я думаю, что эта установка должно быть всегда. Установка xfail_strict = true
приводит к тому, что тесты, помеченные @pytest.mark.xfail
, не распознаются, как вызвавшие ошибку. В разделе "Маркировка тестов ожидающих сбоя" на стр. Дополнительные сведения о маркере xfail
см. 37.
Предотвращение конфликтов имен файлов
Однако разница между тем, чтобы иметь их или не иметь, проста. Полезность наличия файла __init__.py
в каждом тестовом подкаталоге проекта долго меня смущали. А если нет, то так сделать не получится. Если у вас есть файлы __init__.py
во всех ваших тестовых подкаталогах, вы можете иметь одно и то же тестовое имя файла в нескольких каталогах.
Каталог a
и b
оба имеют файл test_foo.py
. Вот пример. Неважно, что эти файлы содержат в себе, но для этого примера они выглядят так:
ch6/dups/a/test_foo.py
def test_a():
passch6/dups/b/test_foo.py
def test_b():
pass
С такой структурой каталогов:
dups
├── a
│ └── test_foo.py
└── b └── test_foo.py
Запускать их по отдельности получится, а запустить pytest
из каталога dups
нет: Эти файлы даже не имеют того же контента, но тесты испорчены.
$ cd /path/to/code/ch6/dups
$ pytest a
============================= test session starts ============================= collected 1 item a\test_foo.py . ========================== 1 passed in 0.05 seconds =========================== $ pytest b
============================= test session starts ============================= collected 1 item b\test_foo.py . ========================== 1 passed in 0.05 seconds =========================== $ pytest
============================= test session starts ============================= collected 1 item / 1 errors =================================== ERRORS ====================================
_______________________ ERROR collecting b/test_foo.py ________________________
import file mismatch:
imported module 'test_foo' has this __file__ attribute: /path/to/code/ch6/dups/a/test_foo.py
which is not the same as the test file we want to collect: /path/to/code/ch6/dups/b/test_foo.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules
!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!
=========================== 1 error in 0.34 seconds ===========================
Ни чего не понятно!
Это сообщение об ошибке не дает понять, что пошло не так.
Вот пример каталога dups_fixed
такими же дублированными именами файлов, но с добавленными файлами __init__.py
: Чтобы исправить этот тест, просто добавьте пустой __init__.py
файл в подкаталоги.
dups_fixed/
├── a
│ ├── __init__.py
│ └── test_foo.py
└── b ├── __init__.py └── test_foo.py
Теперь давайте попробуем еще раз с верхнего уровня в dups_fixed
:
$ cd /path/to/code/ch6/ch6/dups_fixed/ $ pytest
============================= test session starts ============================= collected 2 items a\test_foo.py . b\test_foo.py . ========================== 2 passed in 0.15 seconds ===========================
Так то будет лучше.
Все, типа, нормально. Вы, конечно, можете убеждать себя, что у вас никогда не будет повторяющихся имен файлов, поэтому это не имеет значения. Я говорю, просто положите эти файлы туда. Но проекты растут и тестовые каталоги растут, и вы точно хотите дождаться, когда это случиться с вами, прежде чем позаботиться об этом? Сделайте это привычкой и не беспокойтесь об этом снова.
Упражнения
Let’s extend that to include a pytest.ini option called nice. In Chapter 5, Plugins, on page 95, you created a plugin called pytest-nice that included a --nice command-line option.
95 вы создали плагин с именем pytest-nice
который включает параметр командной строки --nice
. В главе 5 «Плагины» на стр. Давайте расширим это, включив опцию pytest.ini
под названием nice
.
- Добавьте следующую строку в хук-функцию
pytest_addoption
pytest_nice.py
:parser.addini('nice', type='bool', help='Turn failures into opportunities.')
- Места в плагине, которые используют
getoption()
, также должны будут вызыватьgetini('nice')
. Сделайте эти изменения. - Проверьте это вручную, добавив
nice
в файлpytest.ini
. - Не забудьте про тесты плагинов. Добавьте тест, чтобы убедиться, что параметр
nice
изpytest.ini
работает корректно. - Добавьте тесты в каталог плагинов. Вам нужно найти некоторые дополнительные функции Pytester.
Что дальше
В следующей главе мы рассмотрим использование pytest в сочетании с другими мощными инструментами тестирования. В то время как pytest является чрезвычайно мощным сам по себе—особенно с плагинами—он также хорошо интегрируется с другими инструментами разработки программного обеспечения и тестирования программного обеспечения.
Вернуться Дальше