When software depends on external hardware, capture the data at the system boundary and make it replayable. Deterministic inputs let you debug the application quickly while reserving the real devices for connectivity, timing, field behavior, and final integration testing.
One of the best ways to move faster on hardware-integrated software is to stop making every development cycle depend on the hardware.
I learned that while developing an iOS feature that consumed live positioning data from two external GNSS receivers. The application needed to parse the incoming stream, use it in calculations, update its state, and represent the result in the interface.
The obvious development loop would have been:
- Connect both receivers.
- Recreate a movement sequence.
- Wait for usable positioning data.
- Inspect the result.
- Repeat the entire process after the next code change.
That might sound like integration testing. In practice, it would have turned almost every parsing change, calculation adjustment, state-management fix, and UI update into a field test.
The physical devices would have become the bottleneck for nearly every part of development.
So I built a hardware-independent test harness that replayed captured NMEA logs.
NMEA Was the Boundary Between the Hardware and the App
GNSS receivers do not necessarily hand an application a finished location object. Many transmit a continuous stream of standardized text messages known as NMEA sentences.
A simplified stream looks like this:
$GPGGA,123519,4807.038,N,01131.000,E,1,08,...
$GPRMC,123520,A,4807.038,N,01131.000,E,...
Those messages can carry information such as latitude, longitude, altitude, time, speed, heading, satellite count, and fix quality.
That stream was a useful system boundary.
Instead of requiring the application to receive every test input live from a connected receiver, I could record the stream once and replay it later. From the application’s perspective, the important input was still arriving. The difference was that I controlled when it arrived and could reproduce the same sequence whenever I needed it.
The interesting part was not specific to NMEA.
The useful engineering pattern was to capture the external system’s output at the boundary and make it replayable.
The Harness Separated Two Kinds of Correctness
Before the replay harness, two different questions were tied to the same test loop:
- Does the application correctly parse, transform, store, and display the incoming data?
- Does the real hardware behave correctly under actual connection and field conditions?
Those are both important questions, but they do not need the same environment.
Recorded data could answer application questions:
- Are NMEA sentences being parsed correctly?
- Do the same inputs produce the same calculations?
- Does application state update in the expected order?
- Does the interface represent the positioning data properly?
- Does the app handle incomplete or unusual recorded sequences?
The physical receivers could answer hardware questions:
- Does the connection remain stable?
- How does real timing differ between devices?
- What happens when signal quality changes?
- Are there receiver-specific messages or failures?
- Does the complete system work under real field conditions?
Separating those concerns made failures easier to reason about. If a calculation was wrong against a known log, I could investigate the application without wondering whether the route, signal, or receiver behavior had changed between attempts.
If the same application logic worked against recorded data but failed with a connected receiver, the hardware boundary became the more likely place to investigate.
Deterministic Inputs Changed the Development Loop
Live hardware produces valuable reality, but reality is not always a convenient debugging fixture.
A movement path is hard to reproduce exactly. Satellite visibility changes. Timing changes. Connections can drop. One receiver can behave differently from another. Even when the system is working correctly, the incoming stream may not be identical between two attempts.
That variability matters during integration testing. It is much less helpful when trying to determine whether one line of parsing or calculation code fixed a bug.
Replaying captured logs gave me deterministic inputs. I could:
- Run the same positioning sequence after every change.
- Compare calculations against a known stream.
- Reproduce a failure without recreating the original field conditions.
- Exercise parsing, state, and UI behavior without reconnecting two receivers.
- Build most of the feature while the physical devices were unavailable.
The test harness also made captured failures more valuable. A difficult field condition did not have to remain a one-time observation. Once recorded, it could become a repeatable case for future development and regression testing.
The Simulator Did Not Replace the Hardware
There is an easy mistake to make with a tool like this: treating a successful replay as proof that the complete integration works.
It is not.
A log can reproduce what crossed the boundary, but it cannot fully reproduce the behavior of the boundary itself.
It does not prove that Bluetooth or another transport reconnects correctly. It does not reproduce every timing difference, signal problem, firmware quirk, battery condition, or device-specific failure. It cannot establish that the system behaves correctly during a real field session.
The two GNSS receivers still mattered. They were necessary for final integration and field validation.
They just did not need to participate in every UI change, calculation adjustment, or debugging session.
A simulator is not a substitute for real-device testing.
It is a way to stop real-device testing from becoming the entire development process.
This Pattern Applies Far Beyond GNSS
Most hardware-integrated applications have a boundary where external behavior becomes application data.
That might be:
- Bluetooth sensor packets
- Barcode scanner events
- Wearable telemetry
- Camera frames and metadata
- Industrial equipment messages
- External medical or measurement-device output
- Responses from a specialized network appliance
If that boundary can be captured faithfully, it can often be replayed through the same application-facing interface.
The design usually benefits from a small abstraction between the data source and the code that consumes it. One implementation reads from the real device. Another reads from a recording. The parser, calculations, state transitions, and interface should not need to know which source produced the stream.
That architecture does more than make testing easier. It keeps hardware-specific concerns from spreading through the rest of the application.
Capture the Boundary, Then Test Each Layer Honestly
When I approach an external-device integration, I now want to identify the earliest stable boundary that the application owns.
Then I want to make the data crossing that boundary observable, recordable, and replayable.
The resulting workflow is straightforward:
- Capture representative output from the real device.
- Preserve the timing and structure that matter to the application.
- Replay it through the same interface the production source uses.
- Test application behavior against deterministic inputs.
- Return to the physical device for the questions only the physical device can answer.
This does not reduce the importance of integration testing.
It makes integration testing more focused.
The real hardware is still the source of truth for real hardware behavior. The replay harness gives the rest of the application a faster, repeatable development loop.
For this iOS integration, that distinction allowed most of the software work to continue without keeping two GNSS receivers attached to every iteration.
The larger lesson is simple:
When hardware becomes the bottleneck, do not immediately ask how to get more access to the hardware.
First ask whether the application can learn from a recording.
What is the most useful fake device, simulator, or replay harness you have built?