assert-pytest-test-can-fail
Assert that every pytest test is able to fail.
Why Must a Test Be Able to Fail?
A test earns its place by being able to fail. A test whose assertion is satisfied on every path it can reach runs, costs what it costs, and reports green for the whole life of the thing it claims to watch. Coverage counts it. The suite counts it. The only way anybody finds out is by reading it.
One shape produces this reliably. A test sets a flag to False, asks a remote
service about a resource, sets the flag to True when the call succeeds, and
sets the same flag to True again in the handler for the error that means the
resource is absent. Every other error is re-raised. Then it asserts the flag.
Every path that reaches the assertion has set it to True, so the assertion is
decoration, and a genuinely missing resource is reported as present.
Mutation testing answers this question in general and answers it better, by mutating the code under test and seeing whether a test dies. It cannot answer it for a test with no code under test — one that asserts about a deployed resource through a client library — and it costs a suite run per mutant. This tool asks a narrower question that a parse can settle.
Installation
pip install assert-pytest-test-can-fail
Usage
Command Line
# Scan specific files
assert-pytest-test-can-fail test_example.py
# Scan directories recursively
assert-pytest-test-can-fail test/
# Use glob patterns
assert-pytest-test-can-fail "test/**/test_*.py"
# Exclude patterns
assert-pytest-test-can-fail test/ --exclude "**/conftest.py"
# Verbose output
assert-pytest-test-can-fail test/ --verbose
# Fail fast (exit on first finding)
assert-pytest-test-can-fail test/ --fail-fast
# Warn only (always exit 0)
assert-pytest-test-can-fail test/ --warn-only
GitHub Actions
- uses: 10U-Labs/assert-pytest-test-can-fail@latest
with:
files: "test/"
verbose: "true"
As a Python Module
python -m assert_pytest_test_can_fail test/
Output Format
Default output shows one finding per line:
path/to/test_file.py:23:test_bucket_exists: every path to the assert sets the flag, so the test cannot fail
Format: file_path:line_number:function_name, followed by the reason. The line
number is the assert statement.
Exit Codes
0: No findings (or--warn-onlyspecified)1: Findings detected2: Error (missing files, syntax errors, etc.)
What Is Reported
A function whose name begins with test is reported when all of these are
true:
- it contains a
trystatement with at least oneexcepthandler - the statement that ends the
trybody — or theelseclause, when there is one — assignsTrueto a bare name - every handler on that
tryis closed: it ends inraise, or it ends assigningTrueto that same name, or it ends in anif/elsewhose two branches each do one of those - an
asserton that bare name follows the wholetrystatement
The last one matters. An assertion sitting inside the try, or above it, is
asking a different question, and is left alone.
Each reported function is reported once, at the first assertion that qualifies.
What Is Never Reported
The shape alone is not the defect, and a rule that says otherwise is a rule
nobody keeps. A permission check written this way is correct: it asks whether
the credentials may inspect a resource, a 404 answers yes, and any error code
the handler does not name leaves the flag False, so the assertion fails.
def test_can_describe_bucket():
has_permission = False
try:
client.head_bucket(Bucket=name)
has_permission = True
except ClientError as error:
code = error.response["Error"]["Code"]
if code == "403":
pytest.fail("No permission to inspect the bucket")
if code == "404":
has_permission = True
assert has_permission
That handler ends in an if with no else, so it can complete with the flag
still False. It is not reported. Neither is a handler that ends in pass,
nor a pytest.fail that leaves other codes falling through — only raise and
an assignment on every branch close a handler.
Also never reported:
- a
trywith noexcepthandler at all, such astry/finally - a success path that computes the flag rather than asserting it, such as
exists = client.bucket_exists(name) - a chained assignment,
exists = asked = True, or an attribute target,record.exists = True - an assertion on anything but a bare name, such as
assert count == 1 - a
tryinside a nested function, a lambda or a class the test defines: the tool does not descend into a scope the test opens - a function whose name does not begin with
test
Only files pytest would collect are scanned: test_*.py and *_test.py.
Options
| Option | Description |
|---|---|
--exclude PATTERNS |
Glob patterns to exclude (comma-separated) |
--quiet |
Suppress all output (exit code only) |
--count |
Output only the number of findings |
--verbose |
Show detailed processing information |
--fail-fast |
Exit after first finding |
--warn-only |
Always exit 0, even with findings |
License
Apache 2.0 - See LICENSE.txt
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
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file assert_pytest_test_can_fail-20260908004717.tar.gz.
File metadata
- Download URL: assert_pytest_test_can_fail-20260908004717.tar.gz
- Upload date:
- Size: 27.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
650f3b7c6c8601354a48b722632787c830eea380b4fbde83ee4f690a511b9969
|
|
| MD5 |
cfc78092996fd4c7b6535aff5862cade
|
|
| BLAKE2b-256 |
2bc73ac26b9c367ae2e05c653c7ebf51982103c05f0e1fd661ee56dce14f7e77
|
Provenance
The following attestation bundles were made for assert_pytest_test_can_fail-20260908004717.tar.gz:
Publisher:
release.yml on 10U-Labs/assert-pytest-test-can-fail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
assert_pytest_test_can_fail-20260908004717.tar.gz -
Subject digest:
650f3b7c6c8601354a48b722632787c830eea380b4fbde83ee4f690a511b9969 - Sigstore transparency entry: 2754068319
- Sigstore integration time:
-
Permalink:
10U-Labs/assert-pytest-test-can-fail@3dac9cfddf3d11915a9316433885b28cfdff2609 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/10U-Labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3dac9cfddf3d11915a9316433885b28cfdff2609 -
Trigger Event:
push
-
Statement type:
File details
Details for the file assert_pytest_test_can_fail-20260908004717-py3-none-any.whl.
File metadata
- Download URL: assert_pytest_test_can_fail-20260908004717-py3-none-any.whl
- Upload date:
- Size: 12.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf6e398819373dcefa4cecd864347c10d126809dc371182de21d29ffdda93715
|
|
| MD5 |
7655534de67067082c36687cc67c4ee8
|
|
| BLAKE2b-256 |
2cfc3c7676f1e2fabfebec03c622933f30a8dea35b83053d3c6f8c3dfb79a869
|
Provenance
The following attestation bundles were made for assert_pytest_test_can_fail-20260908004717-py3-none-any.whl:
Publisher:
release.yml on 10U-Labs/assert-pytest-test-can-fail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
assert_pytest_test_can_fail-20260908004717-py3-none-any.whl -
Subject digest:
bf6e398819373dcefa4cecd864347c10d126809dc371182de21d29ffdda93715 - Sigstore transparency entry: 2754068322
- Sigstore integration time:
-
Permalink:
10U-Labs/assert-pytest-test-can-fail@3dac9cfddf3d11915a9316433885b28cfdff2609 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/10U-Labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3dac9cfddf3d11915a9316433885b28cfdff2609 -
Trigger Event:
push
-
Statement type: