Back to Ideas 6 min read

Twenty Six More Tests, Four Fewer Behaviours

E
EkoHacks Team
·

We had a scoreboard we were pleased with. The mocked suite: 44 tests, 1.31 seconds. The rewritten sociable suite: 70 tests, 13.50 seconds. Slower, obviously, because it talks to a real database, but twenty six tests richer. More thorough. That was the reading, and we published it.

Then we pulled the actual scenario titles out of both suites and compared them line by line, because a count is not a list. The reading did not survive.

Twenty six is a net, and nets hide things

The plus twenty six is two movements in opposite directions. A new WakaTime subsystem, untested before, brought thirty three tests with it. The files that existed in both versions went the other way, from 44 to 37.

That minus seven contains two completely different things, and only one of them is fine.

Some of it is legitimate collapse. The old suite had four assertions shaped like should route push events to handlePushEvent, each checking that a function had been called on a mock. The rewrite replaced them with assertions about what actually happened: dispatches push events to the push handler which records a GitMetric. Once you assert the effect, "was it called" is a question about your own test scaffolding rather than about the system. Collapsing those is right.

The rest is loss.

The four that vanished

BehaviourIn the mocked suiteAfter the rewrite
500 when an event handler throwsshould return 500 if handler throws errorgone
500 when Qodana processing throwsshould return 500 if processQodanaReport throwsgone
400 when the event header is missingshould return 400 for missing event headergone
A report with issues earns no clean scan XPshould handle payload with issues and fixesgone

We checked each one against the rewritten source in case it had survived under a different name. None had. The first three are error paths. The fourth is an ordinary path we simply stopped asserting: the new tests check the clean report thoroughly and never construct a dirty one.

Nobody decided these behaviours had stopped mattering. There was no meeting. They went because the new style made them expensive.

Mocks are very good at exactly one thing

This is the part we would rather not have found, and the part most worth saying out loud.

Every behaviour on that list requires the system to do something other than succeed. With a mock, arranging that is one line: tell the stub to throw. Without a mock you have to construct a collaborator that genuinely fails at the right moment, or build a real fixture in a genuinely dirty state, and that is an afternoon rather than a line.

So injecting failure is free under mocks and deliberate work under everything else. Any honest migration has to budget for it. If you do not consciously reinstate the failure behaviours, the migration deletes them for you, and the test count goes up while it happens, which is the cruel part. The number that would normally reassure you is the number that hides it.

What it did to the comparison

It also meant our scoreboard was never comparing like with like. Two suites that do not cover the same behaviours cannot tell you what a technique costs. So we stopped comparing suites and started comparing implementations of a fixed specification: a verified union of 71 behaviours, being every behaviour the sociable suite really covered, plus the four that had been dropped, reinstated.

Then we built the mocked implementation of that same specification and measured it on the same commit and the same host. All four reinstated behaviours went green, three of them with a single mockRejectedValueOnce each, which is the finding restating itself in the most literal possible way.

Mocked, matchedSociable
Behaviours7167 plus 3 harness tests
Wall clock, mean of ten2.29 s12.80 s

The corrected gap is 5.6 times, where the confounded scoreboard had implied roughly ten. The entire remaining difference is the database.

What we take from it

Test count is not coverage of behaviour, and neither is a coverage percentage, which the first post in this series showed rising as the tests touched less of the system. The only instrument that caught this was reading the scenario titles, by hand, and asking of each one: which behaviour is this, and does the other suite have it.

That is a slow instrument. It is also the one that found four missing behaviours in a suite whose headline numbers were all moving the right way.

Next in this series: what happens to those 12.80 seconds when the database goes behind a Nullable, and whether the fidelity survives it.

E

Written by

EkoHacks Team

More from Ideas

·6 min read

What the Nullable Gave Back

One file, seven behaviours held fixed, the database swapped for a Nullable: about 180 times less time inside the tests, and coverage flat to two decimals.

E
EkoHacks Team
·6 min read

The Best Coverage Number in the Room

Same commit, same spec, same test count. The mocked suite ran 5.6 times faster, covered 3.5 fewer points of real code, and posted the best branch coverage.

E
EkoHacks Team
·7 min read

We Promised Not to Watch. Then We Needed to See.

Our privacy policy said no analytics, and it was true. Adding a page counter meant reopening that promise in public: what we chose, what we refused, one commit.

E
EkoHacks Team