# 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`** — uses `Sam & Dave's "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 1. **No Compose UI tests** for any of the six screens. Verification was manual screenshots. This is the largest single gap. 2. **No battery measurement** over a multi-hour ride. 3. **No map memory-leak measurement** across repeated navigation, despite the osmdroid lifecycle being a known hazard. 4. **No rotation/state-retention testing.** 5. **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. 1. Start, pocket the phone, ride, stop — matching actual usage 2. Max speed plausible against the speedometer 3. Distance plausible against the odometer 4. Moving time excludes stops 5. **Elevation gain near zero on flat ground** — the most likely silent bug 6. Path renders with no straight line across a pause 7. Speed colouring visibly varies along the path 8. **A short ride (under 100 m) still shows streets** — the v2.0 zoom bug 9. GPX opens correctly in Google Earth or Strava 10. Battery drain over a multi-hour ride is acceptable 11. Pause/resume survives a screen-off stretch