Extending pytest with Hooks and Plugins
Mor Dabastany???
Automation and Infrastructure lead || Python and Playwright advocate
It's no secret that I believe that pytest is on of the most powerful Python based test framework, one of the great features of it is the ability to be extended through hooks and plugins. Hooks let you tap into specific points during the testing process, while plugins provide a way to bundle hooks and other pytest extensions into a single distributable package.
Utilizing Hooks
Hooks are a way to extend or modify pytest's behavior without having to modify the core pytest code. pytest provides numerous hooks that you can define and implement in your tests. Here's an example of how to use the pytest_runtest_setup hook to perform an action before each test is run:
import pytest
def pytest_runtest_setup(item):
# Called before each test is run
print(f"Setting up test: {item.name}")
In this example, the pytest_runtest_setup hook will print a message before each test is executed. pytest will automatically discover and call this hook function during the test run.
Creating a Plugin
While hooks are useful for extending pytest's behavior in a single project, plugins allow you to package your hooks and other pytest extensions into a distributable package that can be shared and used across multiple projects.
To create a plugin, you'll need to create a Python package with a pluggy module module that defines the entry points for your plugin. Here's an example directory structure:
# hooks.py
import pytest
def pytest_runtest_setup(item):
print(f"Setting up test: {item.name}")
In setup.py, you'll need to define the entry point for your plugin:
# setup.py
from setuptools import setup
setup(
name="my_pytest_plugin",
packages=["my_pytest_plugin"],
entry_points={
"pytest11": ["my_plugin = my_pytest_plugin.hooks"]
}
)
Once your plugin is installed, pytest will automatically discover and load it. You can then use the hooks and other extensions provided by your plugin in your tests.
Example: Creating a Performance Monitoring Plugin
Let's create a plugin that measures the execution time of each test and prints a report at the end of the test run. First, create a new directory for your plugin:
pytest_perf_monitor/
__init__.py
plugin.py
setup.py
In the plugin.py, define your hooks and helper functions:
# plugin.py
import time
from typing import Dict
test_durations: Dict[str, float] = {}
def pytest_runtest_setup(item):
test_durations[item.nodeid] = time.time()
def pytest_runtest_teardown(item):
start_time = test_durations.pop(item.nodeid)
duration = time.time() - start_time
print(f"Test '{item.nodeid}' took {duration:.6f} seconds")
def pytest_terminal_summary(terminalreporter):
print("\nTest Durations:")
for test_id, duration in test_durations.items():
print(f" {test_id}: {duration:.6f} seconds")
In the setup.py module, define the entry point for your plugin:
# setup.py
from setuptools import setup
setup(
name="pytest_perf_monitor",
packages=["pytest_perf_monitor"],
entry_points={
"pytest11": ["perf_monitor = pytest_perf_monitor.plugin"]
}
)
After installing your plugin, pytest will automatically load it, and you'll see the execution time for each test and a summary report at the end of the test run.
Creating a pytest Plugin with Cookiecutter
While you can create a pytest plugin manually by setting up the directory structure and files, there's an easier way to get started using Cookiecutter. Cookiecutter is a command-line utility that creates projects from project templates, allowing you to create a pytest plugin with a consistent structure and best practices out of the box.
more information can be found here (pytest-dev/cookiecutter-pytest-plugin: A Cookiecutter template for pytest plugins ?? (github.com)
I must add that personally, I found that creating plugins manually is faster when you know the basics, but it won't hurt to learn another way to do it, isn't it?
Conclusion
Hooks and plugins are powerful features that allow you to extend and customize pytest's behavior to suit your needs. Hooks let you tap into specific points during the testing process, while plugins provide a way to bundle hooks and other pytest extensions into a single distributable package. By leveraging these features, you can enhance your testing workflow and gain greater control over the testing process.
#automation #pytest #plugins