Testing & Release
The test pyramid on a device, getting through app review, and finding out what is broken before your users tell you.
You Cannot Hotfix a Phone
Three things follow from it: test more before shipping, roll out gradually so a bad build reaches few people, and instrument well enough that you learn about a crash from your dashboard rather than from a one-star review.
Unit & UI Tests
The pyramid holds, and the reason is speed. Unit tests run in seconds on the JVM or in a simulator; UI tests need a device, take minutes, and fail for reasons that have nothing to do with your code.
| Layer | Covers | Speed |
|---|---|---|
| Unit | ViewModels, mappers, business rules | Milliseconds |
| Integration | Repository over a real local database | Seconds |
| UI | Critical flows end to end | Minutes, flaky |
| Manual | Feel, gestures, real hardware | Irreplaceable |
Architecture decides how much is testable. A ViewModel with injected dependencies and no framework types is a plain unit test; logic embedded in an Activity or a View needs a device to run at all.
App Store Submission
Both stores review builds, and both reject for reasons that are policy rather than engineering. Read the guidelines before building the feature, not after the rejection.
| Trips people up | Detail |
|---|---|
| Payments | Digital goods generally must use the store's billing, with its cut |
| Privacy disclosure | Declare every piece of data collected, including via SDKs |
| Permissions | Each one needs a clear justification string, asked in context |
| Account deletion | If you allow sign-up in the app, you must allow deletion in it |
| Sign-in options | Third-party login may require offering a privacy-preserving one |
Ship through the release channels rather than straight to everyone: internal testing, then a closed beta (TestFlight or a Play track), then a staged rollout — 1%, 10%, 50%, 100% — watching crash-free rate at each step, and halting the moment it dips.
Crash Reporting & Analytics
After release your visibility is whatever you instrumented. The baseline is a crash reporter with symbolicated stack traces, device and OS breakdown, and the app version attached — without symbols a crash report is a list of addresses.
| Watch | Because |
|---|---|
| Crash-free sessions | The one number that gates a rollout |
| ANRs / hangs | A frozen app is a crash to the user, but is not reported as one |
| Cold start time | The first thing every user experiences |
| Funnel drop-off | Where people give up — usually somewhere you never tested |
| Adoption per version | How long the old broken build stays alive out there |
Feature flags are the closest thing to a hotfix you get. A risky feature behind a remote flag can be turned off in minutes instead of waiting out a review cycle.
Interview Questions
Why is mobile release riskier than web?
You cannot revert. A bad build needs a new submission, a review, and then users to update — some never do — so a defect lives on devices for weeks.
What is a staged rollout?
Releasing to a small percentage first and increasing it while watching crash-free rate. It limits the blast radius of a defect and gives you a signal before everyone has the build.
Why keep UI tests few?
They are slow and flaky — animations, keyboards, permission dialogs and network. A large flaky suite gets ignored, so cover only the flows that must never break.
What makes code testable on mobile?
Keeping logic out of framework types. A ViewModel with injected dependencies is a plain unit test; the same logic inside an Activity or View needs a device.
What are the common store rejection causes?
Bypassing store billing for digital goods, incomplete privacy disclosure, permissions without justification, and missing in-app account deletion when sign-up is offered.
Why do crash reports need symbols?
Release builds are optimised and stripped, so a raw trace is memory addresses. Uploading the symbol files at build time is what turns them back into readable stack traces.