Lesson 14 / 25

Detecting Test Tampering

Removed assertions, skips and trivial tests.

Look at what changed in tests

The most common way to "fix" a failing test without fixing the code is to change the test: delete assertions, add skip or xfail markers, loosen expected values, or replace checks with assertions that are always true. A tampering detector scans the patch for these patterns. Simplest policy for autonomous fixes: test files are read-only; tests are added or changed only in a separate, human-reviewed step.

Block the cheats and the leaks

Mechanical checks stop test tampering, edits to protected files and leaked secrets.

Three ideas: test tampering, protected paths, secrets.
Figure 5.1 — Tampering, protected paths and secrets.

Scanning patches for test tampering, run

I ran this with Python 3 (standard library) and, where it uses git, real git in a throwaway temporary repository. Candidate patches are written by hand to stand in for model output. An honest one-line code fix is clean. A cheating patch is flagged three times: an assertion removed, a skip decorator added, and a trivial assertTrue(True) added. Pattern checks catch common cases; making tests read-only closes the loophole entirely.

import re
def review_test_changes(diff):
    findings = []
    for line in diff.splitlines():
        if line.startswith("-") and re.search(r"\bassert", line) and not line.startswith("---"):
            findings.append("assertion removed: " + line[1:].strip())
        if line.startswith("+") and re.search(r"@(unittest\.)?skip|pytest\.mark\.(skip|xfail)", line):
            findings.append("test skipped: " + line[1:].strip())
        if line.startswith("+") and re.search(r"assert\w*\(.*\b(True|1)\)\s*$", line):
            findings.append("trivial assertion added: " + line[1:].strip())
    return findings
honest = """--- a/pricing.py
+++ b/pricing.py
-    if code == "FLAT50" and total > 50:
+    if code == "FLAT50" and total >= 50:"""
cheating = """--- a/test_pricing.py
+++ b/test_pricing.py
-        self.assertEqual(discount(50, "FLAT50"), 0)
+    @unittest.skip("flaky")
+        self.assertTrue(True)"""
for name, diff in [("honest patch", honest), ("cheating patch", cheating)]:
    f = review_test_changes(diff)
    print(name, "->", "clean" if not f else f)

Output:

honest patch -> clean
cheating patch -> ['assertion removed: self.assertEqual(discount(50, "FLAT50"), 0)', 'test skipped: @unittest.skip("flaky")', 'trivial assertion added: self.assertTrue(True)']

Make tests read-only for bots

Enforce at the file level (protected paths) instead of relying only on pattern detection.

Quick check: Which change should a tampering check flag?

  • Renaming a local variable in source
  • Changing > to >= in source code
  • Adding a docstring to a function
  • Adding @unittest.skip to the failing test
Answer

Adding @unittest.skip to the failing test — Skipping a test hides the failure.