Files
samplez/rippr-src/docs/TESTING.md
uhryniuk 247b9cdb3f Refresh Rippr snapshot and bundle with full project documentation
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>
2026-08-11 08:44:48 -05:00

5.9 KiB
Raw Blame History

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 <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

  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