How to Write Custom Pytest Plugins for Advanced Test Functionality
Pytest is one of Python’s most extensible testing frameworks, allowing developers to create custom plugins to extend its functionality. Whether you need to add new command-line options, implement custom fixtures, or automate repetitive tasks, Pytest plugins enable you to tailor the framework to your unique requirements.
In this article, we’ll explore how to write a custom Pytest plugin, register it, and integrate it into your test workflows.
What Are Pytest Plugins?
Pytest plugins are Python modules or packages that enhance the core functionality of Pytest. They allow developers to:
- Add custom command-line options.
- Define reusable fixtures.
- Interact with Pytest hooks to modify test behavior.
- Automate common tasks in testing workflows.
Plugins can be local (specific to a project) or distributed via PyPI for others to use.
How to Create a Simple Pytest Plugin
Let’s start by creating a simple plugin that adds a custom marker to track slow tests.
Step 1: Define the Plugin
Create a file named conftest.py
in your project. This file acts as the entry point for Pytest to discover and load custom plugins.
import pytest
# Custom marker for slow tests
def pytest_configure(config):
config.addinivalue_line(
"markers", "slow: mark test as slow"
)
# Hook to skip slow tests if --skip-slow option is used
def pytest_addoption(parser):
parser.addoption(
"--skip-slow", action="store_true", default=False, help="Skip tests marked as slow"
)
def pytest_collection_modifyitems(config, items):
if config.getoption("--skip-slow"):
skip_slow = pytest.mark.skip(reason="Skipped slow tests")
for item in items:
if "slow" in item.keywords:
item.add_marker(skip_slow)
Explanation
pytest_configure
: Registers a new marker (slow
) with Pytest.pytest_addoption
: Adds a custom command-line option (--skip-slow
) to control plugin behavior.pytest_collection_modifyitems
: Modifies test collection to skip tests marked withslow
if the--skip-slow
option is used.
Step 2: Mark Tests
Use the custom marker @pytest.mark.slow
in your test cases:
import pytest
@pytest.mark.slow
def test_slow_function():
assert sum(range(10**6)) > 0
def test_fast_function():
assert 1 + 1 == 2
Step 3: Run Tests
Run tests normally:
pytest
Skip slow tests with the --skip-slow
option:
pytest --skip-slow
This basic plugin showcases how to define custom markers and command-line options.
How to Write Reusable Plugins
To distribute your plugin for reuse, package it as a Python module and publish it to PyPI.
Step 1: Create the Package Structure
Organize your plugin as a Python package:
pytest_slow/
├── pytest_slow/
│ ├── __init__.py
│ ├── plugin.py
├── setup.py
├── README.md
└── LICENSE
Step 2: Define the Plugin
Move the plugin code to plugin.py
inside the pytest_slow
directory.
import pytest
def pytest_configure(config):
config.addinivalue_line(
"markers", "slow: mark test as slow"
)
def pytest_addoption(parser):
parser.addoption(
"--skip-slow", action="store_true", default=False, help="Skip tests marked as slow"
)
def pytest_collection_modifyitems(config, items):
if config.getoption("--skip-slow"):
skip_slow = pytest.mark.skip(reason="Skipped slow tests")
for item in items:
if "slow" in item.keywords:
item.add_marker(skip_slow)
Step 3: Add Setup Configuration
Create a setup.py
file to define the package:
from setuptools import setup
setup(
name="pytest-slow",
version="0.1.0",
py_modules=["pytest_slow.plugin"],
install_requires=["pytest"],
entry_points={
"pytest11": [
"pytest_slow = pytest_slow.plugin",
],
},
description="A pytest plugin to skip slow tests",
long_description=open("README.md").read(),
classifiers=["Framework :: Pytest"],
)
Step 4: Publish the Plugin
- Test Locally: Install the plugin locally for testing:
pip install .
- Publish to PyPI: Use tools like
twine
to upload the package to PyPI.
How to Debug and Test Plugins
Test plugins thoroughly using the Pytest hooks provided in the pytester
plugin:
def test_skip_slow(pytester):
pytester.makepyfile(
"""
import pytest
@pytest.mark.slow
def test_slow():
assert True
def test_fast():
assert True
"""
)
result = pytester.runpytest("--skip-slow")
result.assert_outcomes(passed=1, skipped=1)
This ensures your plugin behaves as expected.
Conclusion
Custom Pytest plugins unlock the potential to streamline and enhance your testing workflows. By defining plugins for tasks like skipping tests, managing environments, or generating reports, you can improve your test suite’s efficiency and maintainability.
Start experimenting with Pytest plugins today and create powerful, reusable tools tailored to your projects!