Re-exported at 46a0726, which adds README.md plus docs/ARCHITECTURE, DEVELOPMENT, TESTING, v1 history including the original brief, and a v3 backlog. 112 files, and the bundle now carries 19 commits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.9 KiB
Testing
What is covered, what is not, and — most importantly — what the emulator structurally cannot verify. Read the last section before trusting any green build.
84 unit tests (JVM, no device) 46 instrumented tests (device required)
Unit tests — app/src/test/
Pure logic is kept free of Android imports specifically so it can be tested here. This is
the pattern Telemetry.kt established in v1 and every later module follows.
| Suite | Covers |
|---|---|
TelemetryTest |
Speed conversion, noise floor, accuracy gate, duration formatting, upload payload shape |
TelemetryUploaderTest |
Upload success, failure, retry, disabled endpoint — against MockWebServer and an in-memory fake DAO |
GeoTest |
Haversine against known references, Douglas–Peucker, bounds, degenerate cases |
RideStatisticsTest |
Distance, moving time, elevation hysteresis, histogram, profile |
AccumulatorTest |
Live accumulation, the cross-batch anchor, restore, segment boundaries |
RideExportTest |
GPX/GeoJSON structure parsed with real parsers, escaping, exact point counts |
The tests that matter most
A few exist because something specific went wrong and must not return:
stationary noisy altitude yields near-zero elevation gain— a naive implementation reported 1498 m of climbing over a parked bike. The bound is a regression guard, not an accuracy claim.distance across many small batches matches one big batch— guards the cross-batch anchor, whose absence under-reports distance by a few percent, invisibly.every raw point is exported with no decimation— catches render-side simplification leaking into exports.geojson coordinates are longitude first— the classic silent error; wrong order plots in the wrong hemisphere.a hostile trip name still produces valid xml— usesSam & Dave's <ride> "fast".
Instrumented tests — app/src/androidTest/
| Suite | Covers |
|---|---|
SchemaTest |
CASCADE deletes, active-trip flow, ordering, COALESCE guards, upload backlog |
TripRepositoryTest |
All lifecycle transitions, idempotency, process-death simulation |
TrackingServiceLifecycleTest |
The real service driven through all five actions |
MergeTest |
Re-parenting, recomputed aggregates, rejections, atomicity |
These tests must use in-memory databases
TrackingServiceLifecycleTest originally ran against the production AppDatabase
singleton and called deleteAll() in setUp/tearDown. Harmless on a throwaway emulator, but
it would have destroyed every recorded ride had the suite ever been run against a personal
phone. It now substitutes an in-memory database through AppDatabase.overrideForTest() and
TripRepository.overrideForTest(), restoring the singletons afterwards.
Any new test that touches the service must do the same.
Timeouts
Service tests poll with a 25 s timeout. This was raised from 10 s when the suite grew —
but note the flakiness that prompted it turned out to have a real bug behind it
(stopSelf() racing a queued START). Raise timeouts only after ruling out a defect.
What the emulator cannot verify
This is the most important section in this document.
It reports zero velocity, always
adb emu geo fix teleports the device. Location.speed is therefore always 0, and
every recorded speedKmh is 0.0.
Unverifiable on the emulator:
- Max speed, average moving speed
- Moving time (everything falls below the 1.5 km/h noise floor, so it stays 00:00:00)
- Speed colouring on the map — the path renders uniformly in the low-speed colour
A green suite says nothing about any of these.
It hides UI consequences of that limitation
This is subtler and it bit us. v2.0 shipped with max speed as the headline figure on the recording screen. On the emulator every value is zero, so a number that never moves looks perfectly normal. On a real ride it read as a frozen, broken screen.
When a value cannot change under test, question whether the UI around it can be judged at all.
Synthetic fixtures hide scale-dependent bugs
The emulator ride fixtures were ~900 m. A real 50 m ride zoomed the map past OSM's maximum tile zoom and rendered an empty grid. The bug was entirely deterministic and entirely invisible to a fixture of the wrong size.
Test short and long rides.
Correlated noise is not uniform noise
The elevation fixture uses uniform ±8 m random noise. Real GPS altitude error is correlated — it wanders rather than jitters. The ~30 m residual drift measured against synthetic noise may behave quite differently in practice.
Not covered at all
- No Compose UI tests for any of the six screens. Verification was manual screenshots. This is the largest single gap.
- No battery measurement over a multi-hour ride.
- No map memory-leak measurement across repeated navigation, despite the osmdroid lifecycle being a known hazard.
- No rotation/state-retention testing.
- No test that a GPX imports cleanly into Strava or Garmin — structure is validated against an XML parser, but schema validity does not guarantee a consumer accepts it.
The real-ride checklist
The only way to validate the emulator's blind spots. Run it after any change to recording, statistics, or the map.
- Start, pocket the phone, ride, stop — matching actual usage
- Max speed plausible against the speedometer
- Distance plausible against the odometer
- Moving time excludes stops
- Elevation gain near zero on flat ground — the most likely silent bug
- Path renders with no straight line across a pause
- Speed colouring visibly varies along the path
- A short ride (under 100 m) still shows streets — the v2.0 zoom bug
- GPX opens correctly in Google Earth or Strava
- Battery drain over a multi-hour ride is acceptable
- Pause/resume survives a screen-off stretch