Emmanuel EbriEmmanuel Ebri
Back to Blog
TestingTechnical DebtEngineering Practice

Tests Are the Only Thing You Still Own

When code becomes cheap, the specification becomes the asset

5 min read

Tests Are the Only Thing You Still Own cover

I shipped a platform with four user roles, a payment engine that stacks four kinds of discount, and no test suite at all.

The decision was defensible when I made it. The domain was moving weekly, the schema changed under me twice, and tests written on Monday would have been deleted on Thursday. Iteration speed was worth more than confidence, and I took the trade knowingly.

That justification expired a long time before I admitted it. What made me finally admit it was not a bug. It was noticing which parts of my own codebase I had started avoiding.

The map of fear

Open your project and list the files you do not want to change. Not the ugly ones, the scary ones. The ones where you would rather add a flag than edit the logic.

For me it is the checkout. It is the subsystem with the most branches, the highest cost of failure, and the largest number of interacting rules, which is precisely the description of the code that most needs tests and least has them. That correlation is not a coincidence. Complexity is what makes tests feel expensive to write, and it is also what makes them necessary.

The list of files you are afraid of is your test backlog, in priority order, and it is more honest than any coverage report.

Why this got sharper, not softer

Here is the argument I did not expect to be making a year ago.

When generating an implementation takes minutes, the implementation stops being the expensive artifact. I can produce three versions of a service function before lunch. What I cannot produce automatically is the answer to the question of which behaviours are correct, because that lives in the business, in the conversations with agents and tutors, in the decision that a discount caps at the remaining balance rather than going negative.

That knowledge exists in exactly two places. Your head, and your tests.

If it only lives in your head, then every regeneration, every refactor, every model-assisted rewrite is a fresh chance to lose it silently. The code will still look right. It just quietly will not do the thing you decided six months ago that it must do.

Tests are the durable half of the system now. They are the part that says what the software is for, in a form that survives the implementation being replaced.

Read them as sentences, not as code. Those are business rules in the only format that checks itself.

Read those two tests as sentences rather than as code. A withdrawal larger than the balance is rejected and the balance does not move. Two confirmations for the same reference produce one settlement. Those are business rules, written down in the only format that checks itself.

What I would write first, and what I would skip

If I were restarting with a fixed budget of twenty tests, none of them would be for components.

Every one would go on a rule that costs money or trust when it breaks. Money math and rounding. The permission boundary that separates a support account from an admin one. The idempotency of anything a provider can call twice. The state machine transitions that are legal and the ones that are not. Those tests are cheap to write because the logic is pure, and they fail loudly for exactly the right reasons.

The next tier is one integration test per critical flow, hitting a real database. Sign up, pay, receive access. Not because the individual pieces are untested, but because the wiring between them is where things actually break in my experience, and no unit test looks at wiring.

What I would skip, without guilt: snapshot tests of rendered trees, tests that assert a component called a function, and anything testing a library I did not write. Those inflate coverage and fail on every intentional change, which trains the team to approve red builds. A suite people learn to ignore is worse than no suite, because it costs time and buys nothing.

The loan metaphor, taken seriously

Skipping tests is borrowing. That framing is old and mostly used as a scold, so let me be specific about the terms, because the terms are what matter.

The principal is the tests you did not write. The interest is paid in a currency you do not notice at first: slightly slower changes, slightly more manual verification, slightly more hesitation. It compounds quietly.

The default event is the day you decide not to make a change you know is right, because the risk of touching that file is higher than the benefit of improving it. That is the moment the debt stopped being financial and became structural. The system is now shaped by your fear of it.

I hit that point on my discount stacking logic. I know how I would restructure it. I have not, because the only way to verify the restructure is to manually walk every combination of points, voucher, referral credit and installment split, and I am not going to do that reliably at eleven at night.

That is the real cost, and it never showed up as a bug report.

Where I actually am

The payment tests are being written now, before anything else, because that is where the fear map points. It is slower than I would like, because writing tests against code that was designed without them means untangling the design first, which is another form of the interest payment.

The thing I would tell someone at the start of a project, holding the same trade-off I held: you are probably right that tests would churn early. Take the loan. Just write down when you will repay it, in terms of a condition rather than a date. When the payment logic stops changing weekly. When the second developer joins. When real money moves through it.

Because the loan does not come due on a schedule. It comes due the day you need to change something and discover you have quietly given up the ability to.

I build EdTech and fintech products at E-Bringgs Technologies. This one is written from the wrong side of the decision.