Python testing with pytest. просто, быстро, эффективно и масштабируемо. предисловие и ведение

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

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

Пример теста¶

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.

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

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

Adblock
detector