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
Metadata
Release files for assert-pytest-test-can-fail 20260908004717
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| assert_pytest_test_can_fail-20260908004717.tar.gz | 27.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| assert_pytest_test_can_fail-20260908004717-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 39.9 kB
Release files / assert_pytest_test_can_fail-20260908004717.tar.gz
| Download URL | assert_pytest_test_can_fail-20260908004717.tar.gz |
|---|---|
| Size | 27.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
650f3b7c6c8601354a48b722632787c830eea380b4fbde83ee4f690a511b9969
|
|
BLAKE2b-256 checksum How to use checksums |
2bc73ac26b9c367ae2e05c653c7ebf51982103c05f0e1fd661ee56dce14f7e77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logRelease files / assert_pytest_test_can_fail-20260908004717-py3-none-any.whl
| Download URL | assert_pytest_test_can_fail-20260908004717-py3-none-any.whl |
|---|---|
| Size | 12.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bf6e398819373dcefa4cecd864347c10d126809dc371182de21d29ffdda93715
|
|
BLAKE2b-256 checksum How to use checksums |
2cfc3c7676f1e2fabfebec03c622933f30a8dea35b83053d3c6f8c3dfb79a869
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logSecurity for assert-pytest-test-can-fail 20260908004717
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for assert_pytest_test_can_fail-20260908004717.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.