Pytest plugin for testing function idempotence.
Project description
pytest-idempotent
Pytest plugin for testing the idempotency of a function.
Usage
pip install pytest-idempotent
Documentation
Suppose we had the following function, that we (incorrectly) assumed is idempotent (AKA we should be able to run it more than once without any adverse effects).
from pytest_idempotent import idempotent # or use your own decorator!
@idempotent
def func(x: list[int]) -> None:
x += [9]
Note: this function is not idempotent because calling it on the same list x
grows the size of x
by 1 each time.
We can write an idempotency test for this function as follows:
# idempotency_test.py
import pytest
@pytest.mark.idempotent
def test_func() -> None:
x: list[int] = []
func(x)
assert x == [9]
Adding the @pytest.mark.idempotent
mark automatically splits this test into two - one that tests the regular behavior and one that tests that the function can be called twice without adverse effects.
❯❯❯ pytest
================= test session starts ==================
platform darwin -- Python 3.9.2, pytest-6.2.5
collected 2 items
tests/idempotency_test.py .F [100%]
===================== FAILURES ========================
------------- test_func[idempotency-check] -------------
@pytest.mark.idempotent
def test_func() -> None:
x: list[int] = []
func(x)
> assert x == [9]
E assert [9, 9] == [9]
E Left contains one more item: 9
E Use -v to get the full diff
tests/idempotency_test.py:19: AssertionError
=============== short test summary info ================
FAILED tests/idempotency_test.py::test_func[idempotency-check]
- assert [9, 9] == [9]
============= 1 failed, 1 passed in 0.16s ==============
How It Works
Idempotency is a difficult pattern to enforce. To solve this issue, pytest-idempotent takes the following approach:
-
Introduce a decorator,
@idempotent
, to functions.- This decorator serves as a visual aid. If this decorator is commonly used in the codebase, it is much easier to consider idempotency for new and existing functions.
- At runtime, this decorator is a no-op.
- At test-time, if the feature is enabled, we will run the decorated function twice with the same parameters in all test cases.
- We can also assert that the second run returns the same result as an additional parameter to the function's decorator:
@idempotent(equal_return=True)
.
-
For all tests marked using
@pytest.mark.idempotent
, we run each test twice: once normally, and once with the decorated function called twice.- Both runs need to pass all assertions.
- We return the first result because the first run should complete the processing. The second will either return exact the same result or be a no-op.
- To disable idempotency testing for a test or group of tests, use:
@pytest.mark.idempotent(run_twice=False)
@idempotent decorator
By default, the @idempotent
decorator does nothing during runtime. We do not want to add overhead to production code to run tests.
from typing import Any, Callable, TypeVar
_F = TypeVar("_F", bound=Callable[..., Any])
def idempotent(func: _F) -> _F:
"""
No-op during runtime.
This marker allows pytest-idempotent to override the decorated function
during test-time to verify the function is idempotent.
"""
return func
To use your own @idempotent
decorator, you can override the pytest_idempotent_decorator
function in your conftest.py
to return the module path to your implementation.
# conftest.py
# Optional: you can define this to ensure the plugin is correctly installed
pytest_plugins = ["pytest_idempotent"]
def pytest_idempotent_decorator() -> str:
"""This links to my custom implementation of @idempotent."""
return "src.utils.idempotent"
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file pytest_idempotent-0.0.6.tar.gz
.
File metadata
- Download URL: pytest_idempotent-0.0.6.tar.gz
- Upload date:
- Size: 9.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.6.0 importlib_metadata/3.7.0 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | dec8eb54267ebbfccd67e76c64a2b5a7ac3d09d72a320de45c3711aec7abe31d |
|
MD5 | c9d72dbe749914f9e0aa3c200165961a |
|
BLAKE2b-256 | a9e3ceea163f98126bd8359024d0ae3b27e37ffe9ac31f65fe70cb41c6c22278 |
File details
Details for the file pytest_idempotent-0.0.6-py3-none-any.whl
.
File metadata
- Download URL: pytest_idempotent-0.0.6-py3-none-any.whl
- Upload date:
- Size: 6.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.6.0 importlib_metadata/3.7.0 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | f155270402a1b35e2b897aeb93e5d9b8c2397917e9ba71114575e29626995db3 |
|
MD5 | 93108080a325273da7b8bcc57fbf2bbd |
|
BLAKE2b-256 | ff8f871f69593d8001f5144b7f671724c36fe1af1e49dc00a0e7f0d8a63945c2 |