team18‐iter1‐testing - snuhcs-course/swpp-2026-project-team-18 GitHub Wiki

JustInTime — Testing Documentation

1. Document Revision History

Version Date Changes
1.0 2026-10-08 Iteration 1: test strategy, suites, results on main 8583f6f (0.10.0), acceptance-test traceability, defects with their regression tests, known gaps

Status: ✅ passed · 🚧 partial or not re-run · ❌ not started


2. Test Strategy

2.1 Test levels

Each level checks one artifact of the design, from the requirements down to single modules.

Level Checks against Our suites Runs on Size
Acceptance Requirements (AT-n in the Requirements page) End-to-end flows, demo checklist; mapped in section 5 Emulator, shared or local server 20 of 32 ATs pass
End-to-end (system) User-visible scenarios PublicSetupTest, DemoFlowTest driven by qa/demo/run.py Emulator (instrumented) 2 flows, 18 steps
Integration Interfaces between app, server and database 8 HTTP suites against a live local server, including the app–server contract check Local server, throwaway SQLite 228 checks
Unit and in-process API Modules and single endpoints Backend pytest; app JUnit tests on the JVM Process or JVM; no device, no network 795 + 528 tests

The suite follows the test pyramid: the 1,323 unit and in-process tests run in about 20 seconds, the HTTP suites in under 30 seconds, and the two end-to-end flows in 5–7 minutes.

flowchart LR
  dev["Developer"] -->|"push to main or pull request"| ci["GitHub Actions"]
  ci --> be["Backend pytest<br/>795 tests"]
  ci --> http["HTTP suites<br/>8 scripts, live local server"]
  ci --> app["App unit tests, lint, debug build<br/>528 tests"]
  dev -->|"on demand"| qa["qa/demo/run.py<br/>UI Automator on an emulator"]
  qa --> local["Throwaway local server"]
  qa --> shared["Shared server, new QA account"]
Loading

2.2 Types of testing

Type Question How we test it in Iteration 1 Status
Functional Does it do what it should? Unit and API tests per module and endpoint; end-to-end flows ✅
Acceptance Does it meet the agreed requirements? ATs mapped to tests (section 5); end-to-end flows; demo checklist 🚧 20 of 32 pass; the rest belong to later iterations
Security Can it be misused? test_security_audit.py (112 tests): another user's id returns 404, authentication and throttles on every view, no tracebacks; owner-scoped local stores (audit tests) ✅
Performance Is it fast enough? Not measured yet (N8–N11). Only timeout and cold-start behavior is tested ❌
Usability Can users finish their tasks? Heuristic evaluation is in Iteration 3; demo rehearsals only so far ❌
Compatibility Does it work on supported devices? Emulators on API 34 and API 36; a physical Galaxy phone only for installation (AT-24 not started) 🚧

2.3 Oracles: what counts as correct

Oracle Used for Example
Specification Acceptance and API rules AT-2: another user's event returns 404, not 403
Hand-computed values Alarm arithmetic AT-5: prep 30 + travel 27 + buffer 10 before 14:30 gives 13:23 exactly
Properties Distributions and estimators Variances add, not standard deviations; the same input gives the same alarm; no probability without variance
Real responses App–server contract The app's DTO field names are compared with responses from a running server
Human judgement End-to-end runs and demos A screenshot and UI dump per step; the D01–D12 demo checklist

2.4 Failures and malformed input

  • Fault injection: tests simulate a failed Kakao route lookup (test_route_failure; the live route reports degraded), a failed address search while place search still works (test_address_failure_preserves_working_place_search), a token refresh that hits a 5xx or a network error (RefreshOutcomeTest), server timeouts and 503s (ColdStartRetryInterceptorTest), and an older server that omits new fields (null-safety tests)
  • Malformed input: malformed_json_is_400, deeply_nested_json_does_not_crash, out-of-range coordinates and τ, inverted block ranges, oversized batches
  • Not used yet: fuzzing at scale, mutation testing

2.5 Isolation and test doubles

  • Backend: config/settings/test.py pins an in-memory SQLite database and blanks every external API key, so client code takes its "no key" path. The Kakao and Seoul clients are replaced with stubs through monkeypatch or mock.patch. Real external calls are checked by hand with check_external_apis.py
  • App: no mocking library. Rules that would need a device are pulled into pure functions and objects (SyncDecision, RouteRefreshDecision, TripGeofence, MorningSession) and tested on the JVM. ColdStartRetryInterceptorTest puts a fake interceptor at the end of the chain and counts calls. Audit tests read src/main to enforce rules that are hard to run without a device: owner-scoped stores, init order, the token guard
  • Integration: each run uses a new SQLite file in the temp folder and a server on a free port
  • End-to-end: a separate QA app (com.swpp.wakeup.qa), a throwaway local server or a new QA account on the shared server, and GPS fixes sent through adb along the Kakao route

2.6 Good-test practices (FIRST)

Evidence
Fast 795 backend tests in 12.5 s; 528 app tests in 8.6 s
Independent Each backend test runs in a transaction that is rolled back afterwards; app tests build their own inputs
Repeatable No network or real keys; time is passed in as a parameter (e.g., RouteRefreshDecision.dueEventIds(alarms, nowMillis)); Monte Carlo uses a fixed seed. CI runs on a clean machine without .env or local.properties
Self-validating Every check asserts. The HTTP runner exits non-zero on any failure; an end-to-end run stops at the first failed step and saves failure.png
Timely Fixes come with a regression test (section 6)

Tests are named after one behavior and its expected outcome, e.g., test_resend_does_not_overwrite or `POST 는 503 이어도 재시도하지 않는다` (a POST is not retried on 503).

2.7 Automation, CI and review

CI job What it runs Fails on
Backend pytest python -m pytest Any failing test
App–server contract scripts/run_local_suite.py: the 8 HTTP suites Any failed check
Android testDebugUnitTest lintDebug assembleDebug A failing test, a lint error or a build error. Lint warnings are reported but don't block
  • CI runs on every push to main and every pull request, with no secrets. Checks that need a Kakao key are skipped, not failed
  • 57 runs since 2026-09-22: 53 passed, 2 failed, 2 cancelled. The failures were the first run while CI was being set up (8c5af19), and 9a07472, where the contract check flagged a field the app reads but the server doesn't send yet (route badges); the check was refined in a21861d
  • In Iteration 1 most changes went to main directly, with CI on every push; one went through a pull request (#1). The team plans to merge through pull requests with CI and review from Iteration 2 (AI Collaboration Report, section 7)
  • Team members specified the behavior and chose the test scenarios; AI wrote parts of the test code and QA scripts to those specifications (AI Collaboration Report, section 1)

3. Test Suites

3.1 Backend unit and API tests (pytest)

backend/apps/*/tests: 26 files, 795 tests. Pure modules (distributions, estimators) are tested directly; endpoints through the Django test client on the in-memory database.

App Tests Covers
accounts 62 Sign-up (consent time, password rules, duplicates in any letter case, rollback when the profile fails), login, JWT refresh, home validation (inside Korea, both coordinates or neither), τ range, a new home recomputing events; the live password check uses the sign-up validators and never echoes the password
common 150 Security audit (112), settings (21), admin (9), health (8)
events 144 Alarm switch and the first-alarm-of-the-day rule (28), events (22), calendar import (26), place search and static map views (45), live route (23)
observations 32 Uploads; a resend is ignored and never overwrites; validation
planning 144 Distributions (49), estimators (35), the alarm computation (22), JSON defaults (14), prep scope (14), faster alternative (10)
prediction 32 Route-correction training: sample filtering, factor and residual spread, minimum samples, clamping
reports 36 Weekly report, lateness causes, calibration buckets and verdicts, isolation
routines 37 Blocks and preconditions, per-event selection (recomputes the alarm), block observations
routing 158 Place search with addresses first (56), real-time arrivals (38), route segments (36), route paths (15), alternatives (13)

3.2 Integration: HTTP suites against a live local server

backend/scripts/run_local_suite.py starts runserver, runs the scripts below in order and stops. They catch what in-process tests miss: URL routing, middleware, real serialization and throttles. The runner pins a throwaway SQLite file, because an emptied variable once sent 21 test accounts to the shared database, and it refuses a busy port, because a leftover server once made the suite test old code.

Script Checks
check_auth_api.py Sign-up, login, refresh and me over HTTP
check_events_api.py A new account has no events; creating one lists it with its alarm plan
check_route_api.py Route candidates → choose one → the alarm uses it → change it and recompute
check_origin_api.py A chosen origin is saved on the event and used by the computation
check_observations_api.py Resends add no rows; delay against the plan has the right sign
check_timezone.py Times sent in KST are stored as UTC and the alarm arithmetic holds
check_routines_api.py Routine endpoints through routing, middleware, serialization and throttles
check_app_contract.py The app's DTO field names match real server responses

Run by hand, outside CI:

  • check_db_defaults.py: read-only check on the shared Postgres that columns added later have a database default. A missing one once made writes fail with 500 while reads and health looked normal, because migrations reach the database before the new code is deployed
  • check_external_apis.py: the external keys work
  • check_deployed.py: the deployed server end to end

3.3 App unit tests (JVM)

APP/app/src/test: 57 files, 66 classes, 528 tests (JUnit 4, kotlinx-coroutines-test).

Package Tests Covers
domain/model 224 Route progress, ETA, lateness outlook and arrival verdict, map camera and projection, morning session, routine blocks and the block form, weekly report view, calendar import
sensing 61 Departure and arrival rules (TripGeofenceTest, 44), trip state, trip notifications, background ownership of live-route calls
data/remote 65 DTO null safety under Gson, token-refresh outcomes, cold-start retry, server version check, place search parsing
data/local 32 Restoring files saved by older versions, cache age labels, owner rules and audits
background 19 When to re-register alarms and retry uploads (SyncDecisionTest), route refresh rules
ui/* 120 Screen state and formatting: sign-up gating, 12-hour time, route choice styles and timeline, map pick, place search, HomeViewModel init order
data/repository 6 alarm_on mapping, relative day labels
(root) 1 Template test

3.4 End-to-end tests (instrumented)

Built by Junha Park. qa/demo/run.py builds the QA app, prepares the server and the emulator, and runs two UI Automator tests on the real UI.

Test Steps
PublicSetupTest (8) Sign up · home by road-name address · prep time · two routine blocks · an event with a place-name destination and a walking route (date and time pickers, including AM/PM and midnight) · alarm reasoning and map · re-login keeps the data · alarm switch off and on
DemoFlowTest (10) Log in · plan and map · arm the alarm and lock the screen · the real alarm rings · dismiss it and finish the prep blocks · depart and move · stay at the destination (the app's real 50 m / 120 s rule) · arrival shown after tracking stops · arrival shown after re-entering the screen · observations uploaded and counted in this week's report
Mode Server Needs
default Throwaway local server A Kakao key in backend/.env
--public-ui Shared server, new QA account; setup steps only Nothing
--public-e2e Shared server, new QA account; both tests Nothing
  • Each run saves result.json, server-evidence.json, a screenshot and UI dump per step, logs and optional screen video
  • To keep a run to 5–7 minutes, the event's start time is adjusted through the API: before the alarm, so it rings about 30 s after setup, and after the arrival, so the weekly report counts the event. Prep blocks are declared as 1–2 minutes and marked done after 5 s each; the trip is simulated in 65 s
  • Not covered: push notifications, recovery after reboot or offline periods, process restart, real GPS. The runner works on macOS and Linux only

3.5 Manual checks

  • docs/demo-checklist.md: steps D01–D12 with pass criteria and a result template, for the presentation device
  • Reboot re-registration (AT-6) was checked on the emulator: the alarm is restored from disk at the same time (dumpsys alarm)
  • Physical device: a Galaxy S24 (Android 16) has 0.10.0 installed and launched. No functional checks yet

4. Iteration 1 Results

Suite Where Date Code Result
Backend pytest PM's laptop (Windows, Python 3.12.10) 2026-10-08 main 8583f6f ✅ 795 passed in 12.5 s
App unit tests PM's laptop (JDK 25 from Android Studio) 2026-10-08 8583f6f ✅ 528 passed, 0 skipped
HTTP suites PM's laptop, external keys blanked as in CI 2026-10-08 8583f6f ✅ 8 of 8 scripts; 228 checks passed, 0 failed, 31 skipped (need a Kakao key)
Database defaults Shared Postgres, read-only 2026-10-08 — ✅ 19 columns checked, none at risk
CI GitHub Actions, run 37609461513 2026-10-07 8583f6f ✅ backend, contract, Android (tests, lint, build)
Local end-to-end Junha Park, emulator API 34 2026-10-07 8583f6f ✅ 8 setup + 10 flow steps, twice in a row
Public setup Junha Park 2026-10-05 0.9.0 ✅ 7 steps in 65 s · 🚧 not re-run on 0.10.0
Public end-to-end Junha Park 2026-10-05 0.9.0 ✅ 7 + 10 steps in 342 s; arrival and report 1/1 · 🚧 not re-run on 0.10.0
Shared-server smoke PM, emulator API 36 2026-10-08 Server 0.10.0 ✅ Health reports 0.10.0. Sign-up, home by road-name address and prep time reach Home; road-name addresses come first in search, so the 10-07 search fix is live

HTTP suites in detail:

Script Passed Failed Skipped
check_auth_api.py 22 0 0
check_events_api.py 21 0 4
check_route_api.py 16 0 18
check_origin_api.py 14 0 5
check_observations_api.py 28 0 0
check_timezone.py ✅ (prints no count) 0 —
check_routines_api.py 39 0 2
check_app_contract.py 88 0 2

The Iteration 1 flow (sign in → add an event → the server computes the alarm → it rings → departure and arrival reach the server) passes end to end on the emulator against a local server, and passed on the shared server at 0.9.0.


5. Acceptance Test Traceability

Each acceptance test from the Requirements page and the tests that check it. Test files are under backend/apps/<app>/tests/ or APP/app/src/test/.

AT Story Tests (level) Result
AT-1 US-1 accounts/test_api.py: creates_user_and_profile, requires_explicit_agreement, records_agreement_time, rejects_short_password, rejects_duplicate_email_case_insensitively; accounts/test_password_check.py: check_matches_registration (unit); SignupContractTest (app); check_auth_api.py (integration); setup step 1 (E2E) ✅
AT-2 US-1 common/test_security_audit.py: foreign_resource_never_returns_403 and the other cross-user cases (unit) ✅
AT-3 US-2 accounts/test_api.py: setting_home_recomputes_pending_events (unit); setup step 2 (E2E) ✅
AT-4 US-3 planning/test_services.py: no_place (unit); check_events_api.py (integration); setup step 5 (E2E) ✅
AT-5 US-4 planning/test_services.py: breakdown_sums_to_the_alarm_gap; planning/test_estimators.py: breakdown_sums_to_total (unit); setup step 6 (E2E) ✅
AT-6 US-5 Flow steps 3–4: locked screen, real alarm (E2E); reboot re-registration (manual) ✅ emulator
AT-7 US-6 TripGeofenceTest (app, 44); check_observations_api.py (integration); flow steps 6–9 (E2E) ✅ emulator
AT-8 US-6 observations/test_api.py: resend_is_200_and_ignored, resend_does_not_overwrite; routines/test_api.py: resending_the_same_uuid_is_ignored (unit); check_observations_api.py (integration) ✅
AT-9 US-7 planning/test_estimators.py: range_becomes_mean_and_sd (unit); BlockDraftTest (app) ✅
AT-10 US-8 routines/test_api.py: put_recomputes_the_alarm, put_saves_selection_and_marks_explicit; planning/test_services.py: excluded_block_does_not_count (unit); RoutineEditorStateTest (app) ✅ (N10 not met)
AT-11 US-9 events/test_api.py: true_when_choice_survives (unit); check_route_api.py (integration); setup step 5 (E2E) ✅
AT-12 US-10 MorningSessionTest (app, 20); routines/test_api.py: uploads_and_assigns_the_user (unit); flow steps 5 and 10 (E2E) ✅
AT-13 US-11 SyncDecisionTest (app) ✅
AT-14 US-11 RefreshOutcomeTest (app, 11) ✅
AT-15 US-12 events/test_calendar_import.py (26): second_import_does_not_duplicate, unchanged_import_costs_no_route_call (unit); CalendarImportTest.kt (app) ✅
AT-16 US-13 planning/test_estimators.py: probability_is_none_when_nothing_varies; planning/test_services.py: probability_stays_null, probability_appears (unit) ✅
AT-17 US-14 planning/test_estimators.py: observations_blend_toward_personal, single_observation_keeps_declared_spread; planning/test_distributions.py: blends_variance_not_standard_deviation (unit) ✅
AT-18 US-15 reports/test_reports.py: single_sample_bucket_is_not_reliable, calibrated_is_detected, overconfident_is_detected, conservative_is_detected (unit) ✅
AT-19 US-16 reports/test_reports.py: depart_late_is_measured, unmeasured_causes_are_none_not_zero, nothing_measured_is_labelled_unknown (unit) ✅
AT-27 US-26 events/test_alarm_switch.py (28): recomputed_alarm_order_updates_first_flag, all_explicit_off_has_no_first_alarm (unit); AlarmOnMappingTest (app); setup step 8 (E2E) ✅
AT-20–26, AT-28–32 US-3, 15–25 No tests yet: the features belong to later iterations, and the app has no edit screen for AT-26 ❌

Non-functional requirements with automated checks:

ID Requirement Evidence Result
N3 · N4 Alarms survive a server outage; no sign-out on a network outage SyncDecisionTest, RefreshOutcomeTest ✅
N6 · N7 Failed uploads are retried; resends add no rows SyncDecisionTest; AT-8 tests ✅
N16 · N18 No invented probability; the same input gives the same alarm AT-16 tests; planning/test_estimators.py: is_deterministic; planning/test_distributions.py: is_reproducible_with_a_seeded_rng ✅
N21 · N24 Another user's data returns 404; request limits common/test_security_audit.py, including view_declares_a_throttle; accounts/test_password_check.py: throttle_allows_typing_but_bounds_requests_per_ip ✅
N25 Another account's local data never shows PrefsScopeAuditTest, CacheScopeAuditTest ✅
N32–N37 Test infrastructure CI; check_db_defaults.py ✅
N1 · N2 · N5 No missed alarms; on time within 5 s; re-registration Needs AT-24 on a phone; N5 checked on the emulator only 🚧
N8–N11 · N13–N15 Performance, battery, AI quality Not measured; the calibration report for N15 exists, but data is thin ❌

6. Defects and Regression Tests

Every fixed bug gets a test so it can't come back.

Found in Defect Fix Regression test
App upgrade Dismissing an alarm crashed: an alarm saved by the older version had no prepBlocks, and Gson left the field null Saved data is normalized when read DiskCompatTest (8)
Route choice against an older deployed server The screen crashed when the response had no segments DTO fields that a server may omit are nullable NetworkDtoNullSafetyTest (10), PlaceSearchItemTest
Sign-up against an older deployed server A 2xx response without tokens counted as success, and the user landed back on the login screen Sign-up fails when a token is missing AuthResponseNullSafetyTest (4), AuthTokenGuardTest (3)
Account switch on one device After A logged out, B saw A's morning record Owner-stamped stores; logout clears every store PrefsScopeAuditTest (5), CacheScopeAuditTest (3)
Adding the morning record The app crashed at start: HomeViewModel.init read a property declared below it Declaration order HomeViewModelInitOrderTest (4)
Code review of 0.9.0 The arrival vanished from the screen when tracking stopped Arrival kept apart from the live position TripLiveStateTest (5); flow steps 8–9
Home setup with a road-name address Road-name and lot-number addresses weren't found Complete addresses first, place names as the fallback routing/test_place_search.py: address_wins_over_unrelated_keyword_matches, road_and_jibun_matches_for_same_building_are_deduplicated; setup step 2
Deployment Writes failed with 500 while the database had a new column and the server still ran old code New columns get a database default check_db_defaults.py
CI, 2026-10-05 The contract check failed on a field the app reads but the server doesn't send yet Optional fields that aren't implemented yet are reported separately (a21861d) check_app_contract.py
End-to-end QA, 2026-10-07 Flaky steps: UI nodes went stale after the screen redrew, and minute taps on the clock rounded to 5 minutes Every action finds its node again; the minute hand is dragged; stale retries are logged per step Two full passes in a row

Open defects, without a test yet:

  1. The arrival result is not restored after the app process restarts
  2. When the tracking service restarts, the moving state and the live route are lost
  3. Map panning can jump twice as far during consecutive gestures while a response is slow
  4. A small pinch (12%) snaps a whole zoom level

7. AI Feature Evaluation

To be documented in Iteration 3, as the course schedules. The plan in the full specification:

Module Metric Target Edge cases Status
M1 prep time MAE, quantile calibration ≤ 12 min global, ≤ 7 min personal after 2 weeks (N13) No observations, all-nighter, slept elsewhere, cancelled event Unit-tested; not evaluated on real data
M2 travel correction MAE improvement over raw Kakao ≥ 20% (N14) First trip on a route, weekend headways, heavy rain Unit-tested; not evaluated on real data
M3 natural-language input Place accuracy, tag macro-F1, hallucination rate F1 ≥ 0.85, no invented places Emoji or abbreviation-only titles, online events, mixed language Not started

8. Known Gaps

Gap Effect What closes it
No physical-device run N1 and N2 unverified; power saving and real GPS untested AT-24: 7 mornings on a Galaxy phone
Public end-to-end not re-run on 0.10.0 The last shared-server pass is from 0.9.0 run.py --public-e2e on 0.10.0
The end-to-end runner fails on Windows (WinError 193) Only macOS and Linux machines can run it Windows support in run.py
No coverage measurement We can't see which code no test reaches Coverage reports in CI (Week 7)
Performance not measured N8–N11 unknown; N10 is not met because a block toggle waits for the server Timing on the shared server; battery measurement on a phone
No automated test for password masking in the app's HTTP log N22 rests on code review A unit test for the log redaction
QA accounts stay on the shared server and are not excluded from learning QA trips could skew route corrections (N26) Exclude QA accounts in training, or purge them after each run

9. How to Run

cd backend && python -m pytest                     # 795 tests; in-memory SQLite, no network
cd backend && python scripts/run_local_suite.py    # 8 HTTP suites on a throwaway local server
cd APP && ./gradlew testDebugUnitTest lintDebug assembleDebug   # 528 tests, lint, build
backend/.venv/bin/python qa/demo/run.py --public-e2e --no-video  # end to end on an emulator (macOS/Linux)

Use .venv\Scripts\python.exe and .\gradlew.bat on Windows. Setup for the end-to-end run is in the iteration-1-demo README.

⚠️ **GitHub.com Fallback** ⚠️