visit
”Quality is not an act, it is a habit" - Aristotle
Method | Pros | Cons |
---|---|---|
Drop or skip tests | Fast |
Decreases test coverage |
Fix tests | Proper way :-) | Increases time-to-market |
Auto retry (flaky retry) | The test will run and catch an error if it happens |
Increases test time |
Skip with deadline |
Unblocks you from delivering the current task | Temporary decreases test coverage |
Another way is to add a skip
decorator to fix this test later. But the problem is that time may never come, and the test will be forgotten and skipped forever.
I’ve crafted a simple pytest plugin to get this functionality. The code of the plugin can be found on GitHub: //github.com/bp72/pytest-skipuntil.
pip install pytest-skipuntil
Also, to imitate flaky behavior and repeat our test N-times, we will need the plugin pytestflake-finderr:
pip install pytest-flakefinder
Let’s create a simple app.py to emulate flaking behavior:
import random
class App:
def __init__(self, a, b, fail_ratio=3):
self.choice = [True] + [False]*fail_ratio
self.a = random.randint(1, 100) if random.choice(self.choice) else a
self.b = b
def get_data(self):
if random.choice(self.choice):
raise Exception("oops")
return [1, 2, 3]
import pytest
from datetime import datetime
from app import App
def test_app():
s = App(1, 2)
assert s.a == 1
assert s.b == 2
assert s.get_data() == [1, 2, 3]
(.venv) bp:/mnt/hdd2/projects/python/dummyapp$ pytest --flake-finder --flake-runs=5
====================== test session starts ======================
platform linux -- Python 3.10.12, pytest-7.4.3, pluggy-1.3.0
rootdir: /mnt/hdd2/projects/python/dummyapp
plugins: flakefinder-1.1.0, skipuntil-0.2.0
collected 5 items
test_app.py FF.F. [100%]
====================== FAILURES ======================
______________________ test_app[0] ___________________
def test_app():
s = App(1, 2)
> assert s.a == 1
E assert 8 == 1
E + where 8 = <app.App object at 0x7f8f24b10fd0>.a
test_app.py:9: AssertionError
______________________ test_app[1] ______________________
def test_app():
s = App(1, 2)
assert s.a == 1
assert s.b == 2
> assert s.get_data() == [1, 2, 3]
test_app.py:11:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <app.App object at 0x7f8f24b11060>
def get_data(self):
if random.choice(self.choice):
> raise Exception("oops")
E Exception: oops
app.py:12: Exception
______________________ test_app[3] ______________________
def test_app():
s = App(1, 2)
assert s.a == 1
assert s.b == 2
> assert s.get_data() == [1, 2, 3]
test_app.py:11:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <app.App object at 0x7f8f24ac7820>
def get_data(self):
if random.choice(self.choice):
> raise Exception("oops")
E Exception: oops
app.py:12: Exception
=================== short test summary info ===================
FAILED test_app.py::test_app[0] - assert 8 == 1
FAILED test_app.py::test_app[1] - Exception: oops
FAILED test_app.py::test_app[3] - Exception: oops
=================== 3 failed, 2 passed in 0.01s ===================
@pytest.mark.skip_until(deadline=datetime(2023, 12, 1), strict=True, msg="Alert is suppresed. JIRA-12346")
def test_app():
s = App(1, 2)
assert s.a == 1
assert s.b == 2
assert s.get_data() == [1, 2, 3]
(.venv) bp:/mnt/hdd2/projects/python/dummyapp$ pytest --flake-finder --flake-runs=5
====================== test session starts ======================
platform linux -- Python 3.10.12, pytest-7.4.3, pluggy-1.3.0
rootdir: /mnt/hdd2/projects/python/dummyapp
plugins: flakefinder-1.1.0, skipuntil-0.2.0
collected 5 items
test_app.py sssss [100%]
test_app.py: The test is suppressed until 2023-12-01 00:00:00. The reason is: Alert is suppresed. JIRA-12346
test_app.py: The test is suppressed until 2023-12-01 00:00:00. The reason is: Alert is suppresed. JIRA-12346
test_app.py: The test is suppressed until 2023-12-01 00:00:00. The reason is: Alert is suppresed. JIRA-12346
test_app.py: The test is suppressed until 2023-12-01 00:00:00. The reason is: Alert is suppresed. JIRA-12346
test_app.py: The test is suppressed until 2023-12-01 00:00:00. The reason is: Alert is suppresed. JIRA-12346
====================== 5 skipped in 0.00s ======================
Simulate missed deadlines by setting the deadline arg to the past date. The test started to fail again with a note that the previous suppress was not working anymore.
(.venv) bp:/mnt/hdd2/projects/python/dummyapp$ pytest --flake-finder --flake-runs=5
====================== test session starts ======================
platform linux -- Python 3.10.12, pytest-7.4.3, pluggy-1.3.0
rootdir: /mnt/hdd2/projects/python/dummyapp
plugins: flakefinder-1.1.0, skipuntil-0.2.0
collected 5 items
test_app.py FFF.. [100%]
====================== FAILURES ======================
______________________ test_app[0] ______________________
@pytest.mark.skip_until(deadline=datetime(2023, 11, 1), strict=True, msg="Alert is suppresed. JIRA-12346")
def test_app():
s = App(1, 2)
> assert s.a == 1
E assert 73 == 1
E + where 73 = <app.App object at 0x7f68ee4a0970>.a
test_app.py:9: AssertionError
______________________ test_app[1] ______________________
@pytest.mark.skip_until(deadline=datetime(2023, 11, 1), strict=True, msg="Alert is suppresed. JIRA-12346")
def test_app():
s = App(1, 2)
assert s.a == 1
assert s.b == 2
> assert s.get_data() == [1, 2, 3]
test_app.py:11:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <app.App object at 0x7f68ee4a28f0>
def get_data(self):
if random.choice(self.choice):
> raise Exception("oops")
E Exception: oops
app.py:12: Exception
______________________ test_app[2] ______________________
@pytest.mark.skip_until(deadline=datetime(2023, 11, 1), strict=True, msg="Alert is suppresed. JIRA-12346")
def test_app():
s = App(1, 2)
> assert s.a == 1
E assert 17 == 1
E + where 17 = <app.App object at 0x7f68ee4b5540>.a
test_app.py:9: AssertionError
test_app.py::test_app[0]: the deadline for the test has passed
test_app.py::test_app[1]: the deadline for the test has passed
test_app.py::test_app[2]: the deadline for the test has passed
test_app.py::test_app[3]: the deadline for the test has passed
test_app.py::test_app[4]: the deadline for the test has passed
====================== short test summary info ======================
FAILED test_app.py::test_app[0] - assert 73 == 1
FAILED test_app.py::test_app[1] - Exception: oops
FAILED test_app.py::test_app[2] - assert 17 == 1
====================== 3 failed, 2 passed in 0.01s ======================
The feature of the plugin is that it outputs information about skipped tests and those with passed deadlines. This also can help to keep track of the skipped tests, so they are not forgotten and fixed on time.