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

140 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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