Рубрики
Без рубрики

Написание простого крючка Pytest

Напишите несколько основных крючков pytest для фиксации сбоев тестирования в текстовом файле.

Автор оригинала: Adam Mertz.

Pytest-это мой фреймворк для тестирования Python из-за его гибкости. Одной из его замечательных особенностей является возможность записывать хуки в различные точки выполнения набора тестов. Все они могут быть ссылками через API docs . На работе у нас могут быть сотни тестов, и иногда изменения могут привести к провалу десятков тестов. Теперь давайте не будем слишком углубляться в тему “Когда вы вносите изменения, только несколько тестов должны сломаться. Вы не должны видеть 30-40 провалов тестов.” Ну конечно, это здорово с идеальным набором тестов, но иногда у вас просто не будет этого. Или вы будете вносить изменения в промежуточное программное обеспечение, которое влияет на все вызовы, и у вас будет масса сбоев. Когда у вас есть 30-40 неудачных тестов, если не только 10, вы обнаружите, что просеиваете множество выходных данных pytest, чтобы просто найти тесты, которые потерпели неудачу. Из-за этого давайте просто подключимся к выполнению pytests и запишем все наши провалы тестов в failures.txt таким образом, мы можем очень ясно видеть, какие из наших тестов провалились.

Создайте новый virtualenv и установите в него pytest . Я предполагаю, что Python3.6 Это все, что вам нужно.

Напишите простой тест, который мы заставляем провалиться в файле tests.py

def test_failed():
    assert False
    
def test_passed():
    assert True

Создать conftest.py и добавьте следующий код.

import pytest

@pytest.hookimpl()
def pytest_sessionstart(session):
    print("hello")

Pytest автоматически заберет ваш крючок из conftest.py очень похоже на то, что было бы с светильниками.

Если мы запустим $ pytest tests.py мы можем видеть результат следующим образом. (Для краткости удалил некоторые = )

19:45 $ pytest tests.py 
hello
=======================...=================== test session starts 

В соответствии с api docs мы видим, что это был бы подходящий крючок для создания нашего failures.txt файл так что давайте сделаем это.

Измените крючок следующим образом

# pathlib is great
from pathlib import Path
from _pytest.main import Session

# Let's define our failures.txt as a constant as we will need it later
FAILURES_FILE = Path() / "failures.txt"

@pytest.hookimpl()
def pytest_sessionstart(session: Session):
    if FAILURES_FILE.exists():
        # We want to delete the file if it already exists
        # so we don't carry over failures form last run
        FAILURES_FILE.unlink()
    FAILURES_FILE.touch()

Мне нравится использовать pathlib при работе с чем-либо на пути только потому, что это намного приятнее, чем os.path . Логика изменения довольно проста. Если файл уже существует, удалите его, а затем создайте новый.

Далее нам нужно написать крюк, чтобы добавить тестовые сбои к нашему failures.txt файл. Согласно api docs pytest_run test_make report был бы нашим желаемым крючком. Этот хук запускается сразу после запуска тестового набора и получает сам тестовый набор ( Item ), а также результат тестового набора ( CallInfo ). Добавьте в следующее conftest.py

# ...other imports
from _pytest.nodes import Item
from _pytest.runner import CallInfo

# ...previous code

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item: Item, call: CallInfo):
    # All code prior to yield statement would be ran prior
    # to any other of the same fixtures defined
    
    outcome = yield  # Run all other pytest_runtest_makereport non wrapped hooks
    result = outcome.get_result()
    if result.when == "call" and result.failed:
        try:  # Just to not crash py.test reporting
            with open(str(FAILURE_FILE), "a") as f:
                f.write(result.nodeid + "\n")
        except Exception as e:
            print("ERROR", e)
            pass

Поскольку мы не хотим изменять сам отчет о тестировании, мы напишем наш как завернутый крючок через hookwrapper=True . В api docs give приведен отличный пример упорядочения вызовов hook, который является более глубоким, чем мои комментарии.

В этот момент, если мы запустим $ pytest tests.py мы видим, что наш единственный неудачный тест записывается в failures.txt

$ cat failures.txt 
tests.py::test_failed

Разработчики из вашей команды могут даже не заметить этот файл, поэтому давайте дадим им небольшое направление. Давайте добавим крючок для печати направлений в конце тестового запуска. Снова просматривая api docs оказывается pytest_terminal_summary является нашим желаемым крючком.

# ... previous imports
from _pytest.runner import CallInfo
from _pytest.terminal import TerminalReporter

# ... previous hooks
@pytest.hookimpl(hookwrapper=True)
def pytest_terminal_summary(
    terminalreporter: TerminalReporter, exitstatus: int, config: Config
):
    yield
    print(f"Failures outputted to: {FAILURE_FILE}")
    print(f"to see run\ncat {FAILURE_FILE}")

В нашем случае, я думаю, имеет смысл запустить все остальные крючки до того, как мы запустим наш, чтобы мы могли убедиться, что наши направления-это последнее, что печатается на терминале. Снова мы используем hookwrapper=True и помещаем наш код ПОСЛЕ оператора yield . В нашем случае мы ничего не делаем с результатом yield , поэтому мы можем просто вызвать его и назначить его ничему. С этим изменением кода запустите $ pytest tests.py снова и снова мы видим следующее.

tests.py:3: AssertionError
Failures outputted to: failures.txt
to see run
cat failures.txt
=====================...== 1 failed, 1 passed in 0.04s=====...===

Я надеюсь, что это послужило хорошим реальным примером использования написания пользовательских крючков в pytest . Не стесняйтесь обращаться ко мне с любыми вопросами или отзывами относительно этого поста.