Messy repos fail at the package boundary, not inside helpers. Function tests miss sibling files that share hidden state. Pin the entrypoint outputs with golden files first. Then change exactly one control-flow branch after that.
Why function pins miss the repo
A function pin cannot see import-time file writes. A function pin cannot see CLI argument parsing bugs. A function pin cannot see sibling modules that mutate globals. Package goldens see all three without reading internals.
That is why this workflow starts outside the files. You treat the package as a black box first. Internals become eligible only after the box is pinned.
The failure mode
A helper can look covered and still lie. A second module can write files the helper never sees. Import side effects can change results without a call.
Your refactor then ships a quiet package-level break. This failure is not a style problem. This is a missing lock at the process edge.
Two locks, in order
The first lock freezes every observable package output. The second lock limits the edit to one branch. Skip either lock and the change is a guess.
Record the goldens and commit them first. Only then apply the single-branch source edit.
Example package (illustrative)
The tree below is a teaching fixture, not production code. Treat every command as a local, reproducible example.
messy_quote/
pyproject.toml
src/messy_quote/
__init__.py
cli.py
quote.py
discounts.py
io_util.py
tests/
test_golden_entrypoint.py
record_goldens.py
goldens/
fixtures/
case_retail.json
case_wholesale.json
case_zero.json
The quote module mixes tax, discounts, and logging. The discount module keeps a dead seasonal branch.
The CLI module is the only supported public entrypoint. That entrypoint is the only characterization surface.
Step 1 — Name the only public surface
List commands and imports your other packages already use. Ignore internal helpers during this whole pass. Write the surface on one line and stop.
python -m messy_quote.cli --input fixtures/CASE.json --out ./out
If two entrypoints exist, freeze both of them. Do not freeze private functions in this workflow.
Step 2 — Build a tiny fixture pack
Three cases beat a dozen vague ones. Cover a normal path, a boundary, and an empty input.
{
"id": "retail-001",
"items": [{"sku": "A-1", "qty": 2, "unit_cents": 499}],
"region": "US-CA",
"flags": ["loyalty"]
}
Label these input files as synthetic fixtures. Do not copy production records into the repo.
Step 3 — Record goldens at the process edge
Capture stdout, stderr, and exit code together. Also capture any files the CLI writes under out. Store those captures as committed golden files.
The recorder below is a proposed local harness. Run it only on a revision you already trust.
# tests/record_goldens.py
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
FIX = ROOT / "fixtures"
GOLD = ROOT / "tests" / "goldens"
OUT = ROOT / "out"
def run_case(name: str) -> dict:
out_dir = OUT / name
if out_dir.exists():
for path in out_dir.glob("*"):
path.unlink()
out_dir.mkdir(parents=True, exist_ok=True)
proc = subprocess.run(
[
sys.executable,
"-m",
"messy_quote.cli",
"--input",
str(FIX / f"{name}.json"),
"--out",
str(out_dir),
],
cwd=ROOT,
capture_output=True,
text=True,
check=False,
)
written = sorted(path.name for path in out_dir.iterdir())
return {
"exit_code": proc.returncode,
"stdout": proc.stdout,
"stderr": proc.stderr,
"files": written,
}
def main() -> None:
GOLD.mkdir(parents=True, exist_ok=True)
for case in ("case_retail", "case_wholesale", "case_zero"):
payload = run_case(case)
target = GOLD / f"{case}.json"
target.write_text(
json.dumps(payload, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
if __name__ == "__main__":
main()
Run the recorder once on a known-good revision. Keep that revision free of unfinished refactor work.
python tests/record_goldens.py
git add tests/goldens fixtures
git commit -m "test: freeze messy_quote entrypoint goldens"
That recorder commit is your first lock. Do not edit source files in the same commit.
Step 4 — Assert goldens on every later change
The test must fail on any process-edge drift. Do not assert internal helper return values here.
# tests/test_golden_entrypoint.py
from __future__ import annotations
import json
from pathlib import Path
from record_goldens import run_case
GOLD = Path(__file__).parent / "goldens"
CASES = ("case_retail", "case_wholesale", "case_zero")
def test_entrypoint_matches_goldens() -> None:
for case in CASES:
actual = run_case(case)
expected = json.loads((GOLD / f"{case}.json").read_text())
assert actual["exit_code"] == expected["exit_code"]
assert actual["stdout"] == expected["stdout"]
assert actual["stderr"] == expected["stderr"]
assert actual["files"] == expected["files"]
pytest tests/test_golden_entrypoint.py -q
A failing golden is a stopped refactor, not a style note. Rerun the suite after every source touch. Do not weaken assertions to keep the suite green.
Step 5 — Score the smallest safe change
Use the table for the first source edit. Do not improvise a larger cleanup in that PR.
| Candidate edit | First PR? | Why |
|---|---|---|
Rewrite quote.py
|
No | Touches many branches at once |
| Rename public CLI flags | No | Changes the frozen surface |
| Extract three helpers | No | Moves behavior without a pin win |
| Delete unused import | Yes | No behavior path if goldens hold |
| Remove one dead branch | Yes | Single control-flow change |
| Flip one predicate | Yes | Only if goldens still match |
| Add logging format fields | No | Stderr is part of the lock |
| Change tax rounding | No | Output cents will drift |
The allowed first edit is one branch. One predicate, one return path, one deleted if. Anything larger waits for a second PR.
Step 6 — Make the one-branch edit
The seasonal discount branch never matches any fixture. Delete only that unreachable branch in this pass.
# src/messy_quote/discounts.py
# Before (illustrative)
def apply_discounts(cents: int, flags: list[str], month: int) -> int:
if "loyalty" in flags:
cents = cents - 50
if month == 99: # unreachable seasonal leftover
cents = cents - 200
if "wholesale" in flags:
cents = int(cents * 0.9)
return max(cents, 0)
Leave loyalty and wholesale branches exactly as they are. Do not reformat the rest of the file yet.
# After (illustrative)
def apply_discounts(cents: int, flags: list[str], month: int) -> int:
if "loyalty" in flags:
cents = cents - 50
if "wholesale" in flags:
cents = int(cents * 0.9)
return max(cents, 0)
Re-run goldens before any further cleanup work. If stdout cents change, restore the branch. The golden file is the oracle, not your intent.
Step 7 — Prove the branch was the only hunk
Diff the working tree against lock one. Count behavioral hunks, not cosmetic line noise.
git diff --stat
git diff src/messy_quote/discounts.py
Accept the PR only when three checks hold.
- Goldens still match on all fixture cases.
- The src diff touches one branch in one file.
- CLI flags, file names, and exit codes are unchanged.
If the diff spills into quote.py, revert. Split that work into a later, smaller PR.
Step 8 — Read a golden failure without cheating
Open the failed case file under tests/goldens. Diff the actual stdout against the committed stdout. Classify the drift before you touch source again.
- Whitespace drift means you restore print separators now.
- Integer cents means the branch was not dead.
- File list change means io_util moved a write.
- Exit code change means CLI parsing or an uncaught raise.
- Stderr change means a log line became behavior.
Do not regenerate goldens to hide a red test. Regenerate only after you confirm an intended surface change. This workflow forbids intended surface changes in PR one.
Where a free model belongs
A model should not choose the first lock. Humans record goldens on a known-good revision. A model may propose the single-branch deletion after that.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode offers free model access and a free server option. Use the model only against the decision table above. Paste the golden test and one source file only.
Reject any patch that edits two behavior paths. Run the same pytest command on a clean server. Local hidden files often fake a green suite.
A free server run is a second environment, not a trophy. Keep the command identical to your local golden run.
What this workflow does not claim
It does not prove the quote math is correct. It proves the package still emits the same bytes. Wrong goldens will freeze wrong business rules.
It does not replace unit tests for new code. It does not handle clocks, network, or random seeds. Stub time and I/O before you record, or skip this method.
It does not make large rewrites safe in one PR. The smallest-change rule exists because models over-edit. Treat extra hunks as a failed patch.
Who should not use this
Skip this if you have no stable entrypoint. Skip this for security-sensitive parsers without review. Skip this when output is intentionally non-deterministic.
Skip this if the package has no fixture rights. Greenfield modules need design tests, not goldens. If you can specify behavior, write that spec instead.
Limitations
Golden files rot when you change log format. Stderr locks will block harmless debug lines. Large binary outputs do not belong in git goldens.
Three fixtures will miss rare regions and tax codes. Add a case only when a production bug names it. Do not farm hundreds of synthetic cases on day one.
Process-edge tests are slow compared to unit tests. Keep this golden suite tiny and strict. Put fast helper tests in a later, separate layer.
Closing rule
Freeze the package and commit the golden files. Edit exactly one branch after those files land. If the golden moves, the change was not small.
Restore the branch and pick a smaller hunk. Do not negotiate with a drifting golden file.
Top comments (0)