Python testing with pytest. просто, быстро, эффективно и масштабируемо. предисловие и ведение
Содержание:
- Warning about unraisable exceptions and unhandled thread exceptions¶
- TestRetrieve
- Вызов pytest из кода Python¶
- Изменение Правил Обнаружения Тестов
- Creating JUnitXML format files¶
- Durations Reports: Fighting Slow Tests
- Conclusion
- Выбор выполняемых тестов¶
- Метки: классификация тестов
- Bonus: BDD syntax
- Dropping to PDB (Python Debugger) on failures¶
- TestList
- Упражнения
- Пример теста¶
- Bug Fixes
- Calling pytest from Python code¶
- Что такое pytest?
- Breaking Changes
- Putting it all together
- Создание устанавливаемого плагина
- TestUpdate
- Фикстуры: управление состоянием и зависимостями
- Запуск отладчика PDB (Python Debugger) при падении тестов¶
- Предотвращение конфликтов имен файлов
- xprocess fixture usage
Warning about unraisable exceptions and unhandled thread exceptions¶
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!
Вызов pytest из кода Python¶
Изменение Правил Обнаружения Тестов
pytest находит тесты для запуска на основе определенных правил обнаружения тестов. Стандартные правила обнаружения тестов:
• Начните с одного или нескольких каталогов. Вы можете указать имена файлов или каталогов в командной строке. Если вы ничего не указали, используется текущий каталог.
• Искать в каталоге и во всех его подкаталогах тестовые модули.
• Тестовый модуль — это файл с именем, похожим на или .
• Посмотрите в тестовых модулях функции, которые начинаются с test.
• Ищите классы, которые начинаются с Test. Ищите методы в тех классах, которые начинаются с `testinit`.
Это стандартные правила обнаружения; Однако вы можете изменить их.
python_classes
Обычное правило обнаружения тестов для pytest и классов — считать класс потенциальным тестовым классом, если он начинается с . Класс также не может иметь метод . Но что, если мы захотим назвать наши тестовые классы как или ? Вот где приходит :
Это позволяет нам называть классы так:
python_files
Как и , изменяет правило обнаружения тестов по умолчанию, которое заключается в поиске файлов, начинающихся с или имеющих в конце .
Допустим, у вас есть пользовательский тестовый фреймворк, в котором вы назвали все свои тестовые файлы . Кажется разумным. Вместо того, чтобы переименовывать все ваши файлы, просто добавьте строку в следующим образом:
Очень просто. Теперь вы можете постепенно перенести соглашение об именах, если хотите, или просто оставить его как .
python_functions
действует как две предыдущие настройки, но для тестовых функций и имен методов. Значение по умолчанию — . А чтобы добавить —вы угадали—сделайте это:
Соглашения об именах не кажутся такими уж ограничивающими, не так ли? Так что, если вам не нравится соглашение об именах по умолчанию, просто измените его. Тем не менее, я призываю вас иметь более вескую причину для таких решений. Миграция сотен тестовых файлов — определенно веская причина.
Creating JUnitXML format files¶
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.
Conclusion
Выбор выполняемых тестов¶
поддерживает несколько способов выбора и запуска тестов из командной строки.
Запуск тестов модуля
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
Метки: классификация тестов
В любом большом наборе тестов некоторые тесты неизбежно будут медленными. Например, они могут протестировать поведение тайм-аута или использовать большую часть кода. Какова бы ни была причина, было бы неплохо избегать запуска всех медленных тестов, когда вы пытаетесь быстро перебрать новую функцию.
позволяет вам определять категории для ваших тестов и предоставляет опции для включения или исключения категорий при запуске вашего пакета. Вы можете пометить тест любым количеством категорий.
Маркировка тестов полезна для классификации тестов по подсистемам или зависимостям. Если для некоторых ваших тестов требуется, например, доступ к базе данных, вы можете создать отметку для них.
Когда приходит время для запуска ваших тестов, вы все равно можете запустить их все по умолчанию с помощью команды. Если вы хотите запускать только те тесты, которые требуют доступа к базе данных, то вы можете использовать . Для запуска всех тестов, кроме тех, которые требуют доступа к базе данных, вы можете использовать . Вы даже можете использовать прибор для ограничения доступа к базе данных теми тестами, которые отмечены значком .
Некоторые плагины расширяют функциональность меток, защищая доступ к ресурсам. плагин дает знак. Любые тесты без этой отметки, которые пытаются получить доступ к базе данных, потерпят неудачу. Первый тест, который пытается получить доступ к базе данных, инициирует создание тестовой базы данных Django.
Требование о добавлении метки подталкивает вас к явному указанию ваших зависимостей. Это философия, в конце концов! Это также означает, что вы можете запускать тесты, которые не основываются на базе данных намного быстрее, потому что не позволят тесту инициировать создание базы данных. Экономия времени действительно возрастает, особенно если вы стараетесь часто проводить тесты.
предоставляет несколько отметок из коробки:
пропускает тест безоговорочно.
пропускает тест, если переданное ему выражение оценивается как .
указывает на то, что тест, как ожидается, не пройден, поэтому, если тест не пройден, общий набор может все же привести к прохождению статуса.
(обратите внимание на написание) создает несколько вариантов теста с различными значениями в качестве аргументов. Вы узнаете больше об этой отметке в ближайшее время.
Вы можете увидеть список всех марок, запустив .
Bonus: BDD syntax
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:
TestList
Упражнения
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 вы создали плагин с именем который включает параметр командной строки . Давайте расширим это, включив опцию под названием .
- Добавьте следующую строку в хук-функцию :
- Места в плагине, которые используют , также должны будут вызывать . Сделайте эти изменения.
- Проверьте это вручную, добавив в файл .
- Не забудьте про тесты плагинов. Добавьте тест, чтобы убедиться, что параметр из работает корректно.
- Добавьте тесты в каталог плагинов. Вам нужно найти некоторые .
Пример теста¶
pytest использует assert, чтобы указать какие условия должны
выполняться, чтобы тест считался пройденным.
В pytest тест можно написать как обычную функцию, но имя функции должно
начинаться с . Ниже написан тест test_check_ip, который
проверяет работу функции check_ip, передав ей два значения: правильный
адрес и неправильный, а также после каждой проверки написано сообщение:
import ipaddress def check_ip(ip): try ipaddress.ip_address(ip) return True except ValueError as err return False def test_check_ip(): assert check_ip('10.1.1.1') == True, 'При правильном IP, функция должна возвращать True' assert check_ip('500.1.1.1') == False, 'Если адрес неправильный, функция должна возвращать False' if __name__ == "__main__" result = check_ip('10.1.1.1') print('Function result:', result)
Код записан в файл check_ip_functions.py. Теперь надо разобраться как
вызывать тесты. Самый простой вариант, написать слово pytest. В этом
случае, pytest автоматически обнаружит тесты в текущем каталоге. Однако,
у pytest есть определенные правила, не только по названию функцию, но и
по названию файлов с тестами — имена файлов также должны начинаться на
. Если правила соблюдаются, pytest автоматически найдет тесты,
если нет — надо указать файл с тестами.
В случае с примером выше, надо будет вызвать такую команду:
$ pytest check_ip_functions.py ========================= test session starts ========================== platform linux -- Python 3.7.3, pytest-4.6.2, py-1.5.2, pluggy-0.12.0 rootdir: /home/vagrant/repos/general/pyneng.github.io/code_examples/pytest collected 1 item check_ip_functions.py . ======================= 1 passed in 0.02 seconds =======================
По умолчанию, если тесты проходят, каждый тест (функция test_check_ip)
отмечается точкой. Так как в данном случае тест только один — функция
test_check_ip, после имени check_ip_functions.py стоит точка, а
также ниже написано, что 1 тест прошел.
Теперь, допустим, что функция работает неправильно и всегда возвращает
False (напишите return False в самом начале функции). В этом случае,
выполнение теста будет выглядеть так:
$ pytest check_ip_functions.py ========================= test session starts ========================== platform linux -- Python 3.6.3, pytest-4.6.2, py-1.5.2, pluggy-0.12.0 rootdir: /home/vagrant/repos/general/pyneng.github.io/code_examples/pytest collected 1 item check_ip_functions.py F =============================== FAILURES =============================== ____________________________ test_check_ip _____________________________ def test_check_ip(): > assert check_ip('10.1.1.1') == True, 'При правильном IP, функция должна возвращать True' E AssertionError: При правильном IP, функция должна возвращать True E assert False == True E + where False = check_ip('10.1.1.1') check_ip_functions.py:14: AssertionError ======================= 1 failed in 0.06 seconds =======================
Если тест не проходит, pytest выводит более подробную информацию и
показывает в каком месте что-то пошло не так. В данном случае, при
выполении строки , выражение не
дало истинный результат, поэтому было сгенерировано исключение.
Ниже, pytest показывает, что именно он сравнивал:
и уточняет, что False — это
. Посмотрев на вывод, можно заподозрить, что с
функцией check_ip что-то не так, так как она возвращает False на
правильном адресе.
Чаще всего, тесты пишутся в отдельных файлах. Для данного примера тест
всего один, но он все равно вынесен в отдельный файл.
Файл test_check_ip_function.py:
from check_ip_functions import check_ip def test_check_ip(): assert check_ip('10.1.1.1') == True, 'При правильном IP, функция должна возвращать True' assert check_ip('500.1.1.1') == False, 'Если адрес неправильный, функция должна возвращать False'
Файл check_ip_functions.py:
import ipaddress def check_ip(ip): #return False try ipaddress.ip_address(ip) return True except ValueError as err return False if __name__ == "__main__" result = check_ip('10.1.1.1') print('Function result:', result)
В таком случае, тест можно запустить не указывая файл:
$ pytest ================= test session starts ======================== platform linux -- Python 3.6.3, pytest-4.6.2, py-1.5.2, pluggy-0.12.0 rootdir: /home/vagrant/repos/general/pyneng.github.io/code_examples/pytest collected 1 item test_check_ip_function.py . ================= 1 passed in 0.02 seconds ====================
Bug Fixes
-
#1120: Fix issue where directories from tmpdir are not removed properly when multiple instances of pytest are running in parallel.
-
#4583: Prevent crashing and provide a user-friendly error when a marker expression (-m) invoking of eval raises any exception.
-
#4677: The path shown in the summary report for SKIPPED tests is now always relative. Previously it was sometimes absolute.
-
#5456: Fix a possible race condition when trying to remove lock files used to control access to folders
created by tmp_path and tmpdir. -
#6240: Fixes an issue where logging during collection step caused duplication of log
messages to stderr. -
#6428: Paths appearing in error messages are now correct in case the current working directory has
changed since the start of the session. -
#6755: Support deleting paths longer than 260 characters on windows created inside tmpdir.
-
#6871: Fix crash with captured output when using capsysbinary.
-
#6909: Revert the change introduced by #6330, which required all arguments to to be explicitly defined in the function signature.
The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted.
-
#6910: Fix crash when plugins return an unknown stats while using the option.
-
#6924: Ensure a is actually awaited.
-
#6925: Fix TerminalRepr instances to be hashable again.
-
#6947: Fix regression where functions registered with unittest.TestCase.addCleanup were not being called on test failures.
-
#6951: Allow users to still set the deprecated attribute.
-
#6956: Prevent pytest from printing ConftestImportFailure traceback to stdout.
-
#6991: Fix regressions with —lf filtering too much since pytest 5.4.
-
#6992: Revert «tmpdir: clean up indirection via config for factories» #6767 as it breaks pytest-xdist.
-
#7061: When a yielding fixture fails to yield a value, report a test setup error instead of crashing.
-
#7076: The path of file skipped by in the SKIPPED report is now relative to invocation directory. Previously it was relative to root directory.
-
#7110: Fixed regression: tests are executed correctly again.
-
#7126: now doesn’t raise an error when a bytes value is used as a
parameter when Python is called with the flag. -
#7143: Fix pytest.File.from_parent so it forwards extra keyword arguments to the constructor.
-
#7145: Classes with broken methods are displayed correctly during failures.
-
#7150: Prevent hiding the underlying exception when is raised.
-
#7180: Fix for files encoded differently than locale.
-
#7215: Fix regression where running with would call unittest.TestCase.tearDown for skipped tests.
-
#7253: When using on a function directly, as in ,
if the or arguments are also passed, the function is no longer
ignored, but is marked as a fixture. -
#7360: Fix possibly incorrect evaluation of string expressions passed to and ,
in rare circumstances where the exact same string is used but refers to different global values. -
#7383: Fixed exception causes all over the codebase, i.e. use raise new_exception from old_exception when wrapping an exception.
Calling pytest from Python code¶
Что такое pytest?
Breaking Changes
Putting it all together
Создание устанавливаемого плагина
Процесс обмена плагинами с другими пользователями четко определен. Даже если вы никогда не включите свой собственный плагин в 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. Это сильно обрезанная версия, но она работает.
TestUpdate
The update endpoint should do two things:
- Change the row in the database to match precisely what we’ve POSTed
- Return the updated in the proper structure
Like our retrieve tests, we’ll start by defining the fixture, which we’ll be updating through our request; but we’ll also declare the data we’ll be POSTing
class TestUpdate( UsesPatchMethod, UsesDetailEndpoint, Returns200, ): key_value = lambda_fixture( lambda KeyValue.objects.create( key='pipenv', value='was a huge leap forward', )) data = static_fixture({ 'key' 'buuut poetry', 'value' 'locks quicker and i like that', })
So let’s test that our POSTed data makes it to the database
def test_it_sets_expected_attrs(self, data, key_value): # We must tell Django to grab fresh data from the database, or we'll # see our stale initial data and think our endpoint is broken! key_value.refresh_from_db() expected = data assert_model_attrs(key_value, expected)
$ py.test --tb=no tests/test_kv.py::TestKeyValueViewSet::TestList::test_it_returns_200 <- pytest_drf/status.py PASSED 9% tests/test_kv.py::TestKeyValueViewSet::TestList::test_it_returns_key_values PASSED 18% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_returns_201 <- pytest_drf/status.py PASSED 27% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_creates_new_key_value PASSED 36% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_sets_expected_attrs PASSED 45% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_returns_key_value PASSED 54% tests/test_kv.py::TestKeyValueViewSet::TestRetrieve::test_it_returns_200 <- pytest_drf/status.py PASSED 63% tests/test_kv.py::TestKeyValueViewSet::TestRetrieve::test_it_returns_key_value PASSED 72% tests/test_kv.py::TestKeyValueViewSet::TestUpdate::test_it_returns_200 <- pytest_drf/status.py PASSED 81% tests/test_kv.py::TestKeyValueViewSet::TestUpdate::test_it_sets_expected_attrs PASSED 90% tests/test_kv.py::TestKeyValueViewSet::TestDestroy::test_it_returns_204 <- pytest_drf/status.py ERROR 100%
Excellent.
Now just the standard response structure verification, though this time we’ll have to do the same dance as the last test, for the same reason
def test_it_returns_key_value(self, key_value, json): key_value.refresh_from_db() 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 8% tests/test_kv.py::TestKeyValueViewSet::TestList::test_it_returns_key_values PASSED 16% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_returns_201 <- pytest_drf/status.py PASSED 25% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_creates_new_key_value PASSED 33% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_sets_expected_attrs PASSED 41% tests/test_kv.py::TestKeyValueViewSet::TestCreate::test_it_returns_key_value PASSED 50% tests/test_kv.py::TestKeyValueViewSet::TestRetrieve::test_it_returns_200 <- pytest_drf/status.py PASSED 58% tests/test_kv.py::TestKeyValueViewSet::TestRetrieve::test_it_returns_key_value PASSED 66% tests/test_kv.py::TestKeyValueViewSet::TestUpdate::test_it_returns_200 <- pytest_drf/status.py PASSED 75% tests/test_kv.py::TestKeyValueViewSet::TestUpdate::test_it_sets_expected_attrs PASSED 83% tests/test_kv.py::TestKeyValueViewSet::TestUpdate::test_it_returns_key_value PASSED 91% tests/test_kv.py::TestKeyValueViewSet::TestDestroy::test_it_returns_204 <- pytest_drf/status.py ERROR 100%
Boom! Just the destroy action left!
Фикстуры: управление состоянием и зависимостями
Запуск отладчика PDB (Python Debugger) при падении тестов¶
Предотвращение конфликтов имен файлов
Полезность наличия файла в каждом тестовом подкаталоге проекта долго меня смущали. Однако разница между тем, чтобы иметь их или не иметь, проста. Если у вас есть файлы во всех ваших тестовых подкаталогах, вы можете иметь одно и то же тестовое имя файла в нескольких каталогах. А если нет, то так сделать не получится.
Вот пример. Каталог и оба имеют файл
Неважно, что эти файлы содержат в себе, но для этого примера они выглядят так:
С такой структурой каталогов:
Эти файлы даже не имеют того же контента, но тесты испорчены. Запускать их по отдельности получится, а запустить из каталога нет:
Ни чего не понятно!
Это сообщение об ошибке не дает понять, что пошло не так.
Чтобы исправить этот тест, просто добавьте пустой файл в подкаталоги. Вот пример каталога такими же дублированными именами файлов, но с добавленными файлами :
Теперь давайте попробуем еще раз с верхнего уровня в :
Так то будет лучше.
Вы, конечно, можете убеждать себя, что у вас никогда не будет повторяющихся имен файлов, поэтому это не имеет значения. Все, типа, нормально. Но проекты растут и тестовые каталоги растут, и вы точно хотите дождаться, когда это случиться с вами, прежде чем позаботиться об этом? Я говорю, просто положите эти файлы туда. Сделайте это привычкой и не беспокойтесь об этом снова.
xprocess fixture usage
You typically define a project-specific fixture which uses
internally. Following are two examples:
Minimal reference fixture
# content of conftest.py import pytest from xprocess import ProcessStarter @pytest.fixture def myserver(xprocess): class Starter(ProcessStarter): # startup pattern pattern = "PATTERN" # command to start process args = # ensure process is running and return its logfile logfile = xprocess.ensure("myserver", Starter) conn = # create a connection or url/port info to the server yield conn # clean up whole process tree afterwards xprocess.getinfo("myserver").terminate()
Complete reference fixture
# content of conftest.py import pytest from xprocess import ProcessStarter @pytest.fixture def myserver(xprocess): class Starter(ProcessStarter): # startup pattern pattern = "PATTERN" # command to start process args = # max startup waiting time # optional, defaults to 120 seconds timeout = 45 # max lines read from stdout when matching pattern # optional, defaults to 50 lines max_read_lines = 100 def startup_check(self): """ Optional callback used to check process responsiveness after the provided pattern has been matched. Returned value must be a boolean, where: True: Process has been sucessfuly started and is ready to answer queries. False: Callback failed during process startup. This method will be called multiple times to check if the process is ready to answer queries. A 'TimeoutError' exception will be raised if the provied 'startup_check' does not return 'True' before 'timeout' seconds. """ sock = socket.socket() sock.connect(("localhost", 6777)) sock.sendall(b"testing connection\n") return sock.recv(1) == "connection ok!" # ensure process is running and return its logfile logfile = xprocess.ensure("myserver", Starter) conn = # create a connection or url/port info to the server yield conn # clean up whole process tree afterwards xprocess.getinfo("myserver").terminate()
The method takes the name of an external process and will
make sure it is running during your testing phase. Also, you are not restricted
to having a single external process at a time, can be used to handle
multiple diferent processes or several instances of the same process.