Topic: Compare a testertest*.py and its corresponding test_*.py side-by-side to understand what each convention gains and loses (framework independence vs. fixture reuse, human output vs. structured reporting)

Date: 2026-05-29

Time: 11:29

testertest*.py vs test_*.py: Two Testing Conventions

This codebase maintains two parallel test files for many modules. They look similar at first glance — both use assert, both test the same code — but they serve fundamentally different purposes and make different tradeoffs.

The Core Distinction

testertest*.py files are self-contained scripts. They run with python testertestbtree.py — no test framework needed. Every file has an if _name == 'main': block (testertestbtree.py:190) that calls each test function in sequence and prints human-readable output like "testbasicputget PASSED" (testertestbtree.py:45).

test*.py files are pytest-native. They rely on pytest discovery (function names starting with test), use pytest.raises for exception testing, and in some modules use @pytest.fixture for shared setup (testcdc.py:10, testbitcask.py:10). They produce no output themselves — pytest's runner handles reporting.

Side-by-Side: B-Tree Tests

| Dimension | testertestbtree.py | test_btree.py |

|-----------|----------------------|----------------|

| Lines | 200 | 300+ |

| Test count | 8 | 10+ |

| Dependencies | tempfile, btree only | tempfile, os, struct, btree, WAL internals |

| Output | print("test_X PASSED") per test | None (pytest handles it) |

| Runner | python testertestbtree.py | pytest test_btree.py |

| Error testing | Manual try/except (line 143-149) | pytest.raises |

| Granularity | One fat testbasicputget covers CRUD+split+range | Separate testbasic, testrangescan, testdeleteand_reinsert |

| Internal access | None — black-box only | Imports WAL internals, manipulates file handles directly (testwalrecovery, testwaluncommitted_entries) |

The tester version's testbasicputget (lines 8–45) is a single 37-line function that exercises put, get, contains, split, update, delete, min/max, and close. The pytest version splits this across testbasic (lines 7–56), testrangescan (lines 79–93), and testdeleteand_reinsert (lines 140–161) — each independently runnable and independently reportable.

What Each Convention Gains

testertest*.py gains:

test_*.py gains:

What Each Convention Loses

The tester convention can't do parameterized tests, can't rerun a single test by name, can't integrate with CI without parsing stdout, and duplicates setup boilerplate. The bloom filter tester (testertestbloom_filter.py) imports pytest anyway for pytest.raises (line 4) — so it's not actually framework-independent despite the convention's intent.

The pytest convention requires a dependency. It also hides diagnostic output behind flags — you won't see tree height or page counts unless you add -s. And it doesn't serve the "run this to see what the code does" educational goal at all.

The Pattern Across the Codebase

The two conventions aren't always aligned. The bloom filter pair is instructive: testertestbloomfilter.py (125 lines) and testbloomfilter.py (207 lines) cover largely the same test cases — no-false-negatives, FPR bounds, optimal parameters, counting filter, serialization, union, estimate count, edge cases, determinism. But testbloomfilter.py adds boundary tests the tester skips: testexplicitparameters (line 63), testcountingdoubleaddremove (line 89), testsingleitem (line 140), testduplicateadd (line 146), testunionincompatible (line 118), and testbitarraydensity (line 172).

The tester files are the "proof it works" layer. The test files are the "prove it's correct" layer.

Topics to Explore

Beliefs