Catalin Bozianu

Record reality before you build

I spent last week building Sonario, a mobile app that connects to a sonar device, draws what is under the boat in real time, records the trip with GPS and works offline. Seven days, about a hundred commits, running on a phone, talking to real hardware.

The technique that made it possible is reusable anywhere you depend on something slow or physical, so here it is in full.

The problem

The device makes its own WiFi network. Your phone joins it, the device streams readings, your app draws them.

Which means testing anything requires standing next to the device, with the device in water, on a network with no internet. Every change. A five second development loop becomes twenty minutes and a trip outside.

The two hours that paid for the week

Before writing any of the app, I captured the raw stream from the device into a file. Real readings, real water, including every malformed line and dropout the hardware produces.

Then I wrote a replay service that pretends to be the device and plays the file back at the original rate.

Everything else was built at a desk. The app cannot tell the difference, because the replay emits the same bytes, including the broken ones.

Why a recording beats a simulator

I could have generated plausible data in an hour. It would have been worse than useless, because fabricated data has the shape of your expectations: the fields you remember, the format you assume, the ranges you consider reasonable.

Real captures contain the line that arrives truncated, the three identical timestamps in a row, and my favourite, the depth reading of zero. Zero does not mean the bottom is at the surface. It means no bottom was found. Treat it as a measurement and your display shows the lake bed rising up to meet the boat.

Every one of those broke my code at a desk, in five seconds, instead of at the water in twenty minutes.

How to do this on your own project

The pattern generalises to any slow dependency: a payment provider, a device, a partner API, a scraper, a long running job.

  1. Capture at the lowest level you can. Bytes or raw payloads, not parsed objects. Parsing is code you are going to change.
  2. Capture an unhappy session on purpose. Pull the cable mid stream. Let a request time out. Those recordings are worth more than the clean one.
  3. Replay at real speed by default. Instant replay hides every timing bug you have.
  4. Make the switch one setting, so the same code path runs against both, and nothing is written specially for the fake.
  5. Keep the recordings in the repository and treat them as fixtures. Mine have outlived two rewrites of the code that reads them.

The rest of the week

There was no plugin to speak the device protocol, so I wrote one in native code. In the browser, where that layer does not exist, the app talks to the replay over a websocket instead and the rest of the code never knows.

Both of those took an afternoon each, because I could try things instantly.

That is the real claim. The week was not fast because I typed quickly. It was fast because on Monday I spent two hours making sure Tuesday was cheap.

Back to the diary