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>
This commit is contained in:
139
rippr-src/docs/TESTING.md
Normal file
139
rippr-src/docs/TESTING.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user