Hooks and Customization
The hook is the function pytest calls at a defined point of the run, and the conftest.py (or the plugin) registers it by the name: the pytest_configure (the start), the pytest_collection_modifyitems (the collected list), the pytest_runtest_setup/pytest_runtest_call/pytest_runtest_teardown (the per-item phases), the pytest_runtest_makereport (the report item), the pytest_sessionfinish (the end), and the pytest_terminal_summary (the last line). The customization is the hook that reads or reshapes the run: the skip-by-condition, the order, the per-phase side effect, the failure enrichment, the session note. This chapter covers the hook names (the phases), the pytest_runtest_makereport (the report's build, the retry), the pytest_collection_modifyitems (the order, the deselect), and the config the ini options the hooks read.
The Hook Phases
# the HOOKS: the pytest calls them (the conftest's
# the registration, the name the trigger)
import pytest
def pytest_configure(config):
# the START (the before the collection, the
# the config's the object, the project's setup)
print("the suite starting")
def pytest_collection_modifyitems(items):
# the COLLECTED list (the sort, the deselect)
items.sort(key=lambda i: i.name)
deselect = [i for i in items
if "skipme" in i.name]
config = items[0].config
config.hook.pytest_deselected(items=deselect)
def pytest_runtest_setup(item):
# the PER-ITEM: the before the call (the
# the fixture's after, the assert's before)
if item.get_closest_marker("slow"):
print("the slow test:", item.name)
def pytest_runtest_makereport(item, call):
# the REPORT ITEM (the per-phase, the hookwrap
# the the next section details)
pass # the hookimpl(hookwrapper=True) the full
def pytest_sessionfinish(session):
# the END (the after the last teardown, the
# the session's note)
print("the suite finished")
The hook is the name: pytest calls it at the phase, the conftest registers it, and the plugin does the same (the pytest-cov is the pytest_configure plus the pytest_sessionfinish the measurement). The pytest_configure is the start (the config's object, the project's setup), the pytest_collection_modifyitems is the collected list (the sort, the deselect - the pytest_deselected the report's honesty), the pytest_runtest_setup/call/teardown are the per-item phases (the item the object, the marker read), and the pytest_sessionfinish is the end (the session's, the note). The pytest_runtest_makereport (the next section) is the report's build - the one hook that returns the outcome the rest of the pipeline consumes.
The pytest_runtest_makereport
# the hookwrap: the makereport (the REPORT's build)
import pytest
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
# the rep: the phase (the setup, the call, the
# the teardown) + the outcome (the passed, the
# the failed, the skipped) + the longrepr
if rep.when == "call" and rep.failed:
# the data the JUnit's <properties> carries
item.user_properties.append(
("failed_at", str(call.start - call.stop)))
return rep
# the RETRY: the pytest-rerunfailures plugin is the
# same hook used for the re-run (the --reruns)
The pytest_runtest_makereport is the hook that builds the report item: the @pytest.hookimpl(hookwrapper=True) wraps the call, the yield runs the inner hook, and the outcome.get_result() is the rep (the rep.when the phase, the rep.passed/failed/skipped the outcome, the rep.longrepr the failure detail). The customization is the read or the reshape: the item.user_properties (the data the JUnit's <properties>), the per-phase side effect (the log, the timing), and the rep the returned (the no change, the enriched). The pytest-rerunfailures plugin is the same hook used for the retry (the rep.failed the re-run, the --reruns 2 the count) - the makereport is the report's single build point, and the next section's ini options are the config the hooks read.
The Config the Hooks Read
# the pyproject.toml (the the hooks read):
# [tool.pytest.ini_options]
# testpaths = ["tests"]
# markers = ["slow: the long-running"]
# addopts = "-ra --junitxml=report/junit.xml"
# xfail_strict = true
# filterwarnings = ["error"]
# the the hook's read:
# the config.getini("markers") (the the list)
# the config.stash (the the session's dict, the
# the the configure sets, the the sessionfinish
# the the reads)
The config is the [tool.pytest.ini_options] the pytest_configure and the other hooks read: the testpaths (the search), the markers (the registered), the addopts (the flags the run carries, the -ra --junitxml=report/junit.xml), the xfail_strict (the default), and the filterwarnings (the error the warning's fail). The config.getini("markers") in the hook reads one, and the config.stash (the session's dict) is the hook-to-hook state (the pytest_configure sets, the pytest_sessionfinish reads) - the config is the versioned input, the hook is the code that consumes it, and the rule is the one: the conftest hook is the project's customization, the plugin's hook is the package's, and the ini is the setting both read.
The Working Rules
- The hook is the name, the
conftestthe registration. Thepytest_configurethe start, thepytest_collection_modifyitemsthe list (the sort, the deselect), thepytest_runtest_setup/call/teardownthe phases, thepytest_sessionfinishthe end - the plugin is the same mechanism the package. - The
pytest_runtest_makereportis the report's build, thehookwrapperthe wrap. Theyieldthe inner, theoutcome.get_result()therep(thewhen, the outcome, thelongrepr), theitem.user_propertiesthe JUnit's data - and thepytest-rerunfailuresthe same hook the retry. - The
pytest_deselectedis the report's honesty. Thepytest_collection_modifyitemsthe filter, theconfig.hook.pytest_deselected(items=...)the deselected the shown (the no silent drop, the count the right). - The
iniis the config the hooks read, theconfig.stashthe state. Thegetinithe option, thestashthe session's dict (theconfiguresets, thesessionfinishreads) - the versioned input, the code that consumes.
Summary
The hook is the function pytest calls at the defined point, and the conftest.py (or the plugin) registers it by the name: the pytest_configure (the start, the config's object), the pytest_collection_modifyitems (the collected list, the sort, the deselect plus the pytest_deselected the report's honesty), the pytest_runtest_setup/call/teardown (the per-item phases, the item the object), the pytest_runtest_makereport (the hookwrapper the wrap, the rep the when/outcome/longrepr, the item.user_properties the JUnit's data), and the pytest_sessionfinish (the end). The config is the [tool.pytest.ini_options] the hooks read (the getini, the config.stash the session's dict), and the rule is the split: the conftest hook the project's customization, the plugin's hook the package's, the ini the setting both read. The next chapter's integration testing is the suite against the real dependency (the DB, the HTTP) the hooks and the session fixture serve.