Skip to content
beginner Phase 11 · Mobile Testing

Mobile Testing Approaches

Balance emulators and real devices while covering install, upgrade, and interruption flows.

50m
0 problems
Topic Progress 0%

Emulator, Simulator, or Real Device: Picking the Cheapest Environment That Can See the Bug

A checkout crash shows up on a two-year-old Samsung running Android 12, reproduces on no emulator in the office, and disappears on the flagship everyone tests with. The instinct is "test on more real devices"; the better habit is to ask, for each property under test, what is the cheapest environment that can observe it at all? Because a wrong answer in either direction is expensive: five physical phones running your full suite will not finish before lunch, and a simulator will happily certify battery life it does not have.

Three environments, three different illusions:

Environment What it really is Trust it for Never trust it for
Android emulator full system image under QEMU — real kernel, real framework UI flow, intents, permissions, API calls battery, thermal throttle, radio, OEM quirks
iOS simulator the same OS binaries running natively on your Mac — not an emulator layout, navigation, logic, fast feedback any hardware property; it shares the Mac's CPU
Physical device the actual stack sensors, GPS, camera, push, Doze, performance cheap parallelism; you own and cordon each unit

The iOS distinction is the one that trips teams: an iOS simulator runs the same kernel and libraries as the device, so it boots in seconds and gives correct behavior — but it has no modem, no battery controller, and no thermal envelope, which is why a "100% passing on simulator" suite can ship an app that overheats. The Android emulator is closer to real (it runs an actual system image and, on x86 hosts, translates ARM libraries), yet it still reports a synthetic battery that never drains and a radio that never drops to one bar.

So split the suite by what it claims to prove. Functional flows run headless in the emulator on every pull request — cheap enough to parallelize, and cheap enough that nobody hesitates before running them:

avdmanager create avd -n ci_pixel7 -k "system-images;android-34;google_apis;x86_64"
emulator -avd ci_pixel7 -no-window -no-audio -gpu swiftshader_indirect &
adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 2; done'
adb install -r app-debug.apk

A cold boot lands around 45–90 seconds; the same suite on shared physical hardware costs minutes per device plus queueing, and a shared farm slot is billed while your tests wait for the previous job. Everything the environment fakes runs on real hardware on a schedule: battery drain over a 30-minute playback, thermal throttling during a 10-minute map session, GPS movement, camera permissions in daylight, background sync surviving Doze — nightly or weekly, because that is the budget they need. Reserve owned devices for the paths that hurt most: your top three models by install count, running a 15-minute smoke of login, purchase, and notification receipt. The rule of thumb interviewers accept: emulators for breadth of logic, real devices for truth about hardware and the OS vendor's power management. That split is also what makes automation worth writing at all, which is Mobile Automation with Appium.

The Lifecycle Suite: Install, Upgrade, Permissions, and Interruptions

Most mobile regressions are not in your screens — they are at the edges of the app's life: the install, the upgrade, the permission dialog, and the moment something interrupts. A suite that launches the app and taps through three flows proves none of it, which is why these four groups get named, scheduled, and automated first.

Clean install. Fresh install, first launch, no account, no cached state: onboarding must complete, and the app must not crash when the database is created for the first time. Upgrade in place is the higher-risk sibling and the one teams skip:

adb install -r old-2.9.4.apk     # install the previous release, run its onboarding
# create data: log in, save a draft, change a setting
adb install -r new-3.0.0.apk     # -r keeps user data — this is a real upgrade, not a reinstall
# assert: still logged in, draft intact, settings preserved, no crash on first launch

If the new build migrates the local database, this is the only test that will notice the migration fails — and users who lose their drafts on update do not file bugs, they leave one-star reviews. Test upgrade from every still-supported release, not just the latest one, because a user on 2.7 who jumps straight to 3.0 exercises a different migration path than one on 2.9.

Permissions. Four states, four tests: granted, denied, denied-permanently ("don't ask again" — the app must handle the dead end and route to settings), and first-ask. Drive them without touching the screen:

adb shell pm grant com.acme.shop android.permission.CAMERA
adb shell pm revoke com.acme.shop android.permission.CAMERA
adb shell cmd appops set com.acme.shop CAMERA deny

The classic defect is assuming the grant: the camera screen calls the camera API unguarded and crashes the first time a user says no. Denial must degrade to an explanation, not an exception.

Interruptions. An app on a phone lives in the worst environment in computing — it gets killed, paused, and talked over. Name the scenarios and script them:

Interruption How to trigger (adb) What must survive
incoming call during checkout adb emu gsm call 5551234 state restored on return
airplane mode mid-upload adb shell cmd connectivity airplane-mode enable retry or clear offline message
app switcher kill adb shell am kill com.acme.shop relaunch restores from saved state
rotation / fold adb shell settings put system user_rotation 1 no lost form input
push arrives after force-stop adb shell cmd notification post -t Acme 'Order shipped' deep link opens the right screen
low battery / Doze adb shell dumpsys deviceidle force-idle deferred sync happens later

Each row is a contract with the user: what the app promises to preserve when the platform interrupts it. Notice that half of these are exactly the scenarios automation later has to drive — which is the bridge to the next topic. Finally, none of this is reportable without evidence; that is the third chapter.

The Support Matrix and the Bug Report: Which Devices, and What Evidence Counts

"Test on all devices" is not a strategy; there are tens of thousands of Android models. The product's support matrix is a small, argued document that answers which combinations get tested, and it is derived from exactly three inputs: the declared minimum OS, the newest OS the week it ships, and where your users actually are — the same compatibility reasoning as API Versioning and Compatibility, applied to hardware and OS builds instead of endpoints.

Axis Always include Why
OS floor the declared minimum (e.g. Android 10, iOS 16) you promised it in the store listing
OS ceiling the beta and the newest stable platform changes break apps silently
Hardware spread one low-RAM/older SoC, one current flagship performance cliffs live at the low end
Form factor smallest supported, largest, plus tablet/foldable if claimed layout bugs do not reproduce elsewhere
Vendor skin at least one non-Pixel OEM aggressive battery managers kill background work

The reasoning is worth stating in an interview: the minimum OS catches API misuse the newest SDK forgives, and the newest OS catches platform deprecations the minimum never sees. Neither substitutes for the other, and a matrix containing only flagships on the latest OS tests the environment your developers already own. Pull real numbers into the decision — store console telemetry or analytics showing OS-version and model distribution — so "why do we still test Android 12?" is answered with "18% of sessions", not a preference.

Against that matrix, the bug report is a contractual artifact. The one rule: a mobile finding without device model, OS build, and app version is not a finding — it is a rumor, and it will bounce back to triage within a day.

Device:    Samsung Galaxy A13 (SM-A135F)   ← not "my Samsung"
OS:        Android 12, One UI 4.1, build SP1A.210812.016
App:       3.0.0 (build 30014), installed via APK upgrade from 2.9.4
Network:   Wi-Fi → Wi-Fi off (airplane mode) during upload
Observed:  upload spinner never resolves; app killed after 30s, no error shown
Expected:  offline queue message, retried on reconnect
Evidence:  logcat.txt, screenrecording.mp4, adb bugreport, screenshot attached

Capture the evidence mechanically, not by hand: adb logcat -d > logcat.txt, adb shell screencap -p /sdcard/f.png && adb pull /sdcard/f.png, adb shell screenrecord /sdcard/flow.mp4, and adb bugreport bugreport.zip for the deep cases. Two details separate usable from useless reports: the app build (versionName and versionCode/build number — "happens on 3.0" is ambiguous across three internal builds) and the path to the bug (install history and whether it reproduced on a clean install or only after an upgrade).

Pair the report with one quantitative measurement where the defect is performance-shaped, because "feels laggy" cannot be triaged: startup time via adb shell am start -W -n com.acme.shop/.MainActivity (reports TotalTime in milliseconds), frame jank via adb shell dumpsys gfxinfo com.acme.shop framestats, and memory via adb shell dumpsys meminfo com.acme.shop. Quote the number, the device, and the comparison baseline — "2.4 s cold start on the A13 versus 0.9 s on the Pixel 7" assigns the bug to the low-end hardware path immediately, instead of starting an argument about opinions. Numbers attached to a named device close arguments; adjectives start them.

A matrix that says where to test and a report format that says what happened together convert mobile testing from folklore into a repeatable practice — one a rotating team member can pick up without a week of context.

Quiz

1. Why can an iOS simulator's battery and thermal results never be trusted, even when every test passes?

Question 1 options

2. Which pair of environments must every support matrix include, and why do they catch different defects?

Question 2 options

3. How do you test an Android upgrade path without losing the user's data?

Question 3 options

4. A bug report says 'app crashes on checkout, happens on my Samsung'. What is missing, and why does it matter?

Question 4 options

Flashcards

Question

Emulator vs simulator vs physical device — one line each.

Answer

Android emulator: full system image under QEMU, real kernel — good for functional/intent/permission tests, fake battery and radio. iOS simulator: same OS binaries running natively on the Mac, emulates no hardware. Physical device: the only place sensors, GPS, camera, push, Doze, thermal, and OEM power managers are real.

Question

The four lifecycle groups every mobile suite must contain.

Answer

Clean install (fresh DB, onboarding), upgrade in place (`adb install -r` with data retained, from *every* supported release, verifying migrations and preserved state), permissions (grant / deny / deny-permanently / first ask), and interruptions (call, airplane mode, background kill, rotation, push after force-stop, Doze).

Question

Why does the permission suite include the 'denied permanently' state?

Answer

After 'don't ask again', the system stops showing the dialog, so an app that assumes a grant crashes or dead-ends. The app must detect the state (checkSelfPermission + shouldShowRequestPermissionRationale) and route the user to system settings instead of throwing.

Question

What three inputs define a device support matrix?

Answer

The declared minimum OS (what the store listing promises), the newest OS including beta (platform changes), and real usage telemetry (OS/version and model distribution from analytics). Add hardware tiers and vendor skins because performance and background-kill defects cluster at the low end and in OEM power managers.

Question

The mandatory fields of a reproducible mobile bug report.

Answer

Exact device model (SM-A135F, not 'my Samsung'), OS build incl. vendor skin, app versionName *and* build number, install path (fresh vs upgrade-from), steps, expected vs actual, plus captured evidence: `adb logcat -d`, screencap/screenrecord, and bugreport for deep cases. Missing any of these makes the report a rumor.

Revision Notes

Key Takeaways

  • 1. Choose the environment by the property under test: emulators prove logic cheaply, only physical hardware proves power, sensors, and OEM behavior
  • 2. iOS simulator ≠ emulator — it runs the Mac's CPU with no modem or battery, so hardware metrics from it are fiction
  • 3. Install, upgrade, permissions, and interruptions are where mobile bugs live; launches and happy-path taps are the easy 20%
  • 4. Upgrades must be tested with data retained and from every supported release, because migrations only run along that path
  • 5. A finding without device model, OS build, and app build is a rumor — capture evidence with adb while reproducing, not from memory

Interview Tips

  • • "How do you decide what runs on emulators vs real devices?" — property under test: functional breadth in CI emulators; battery, thermal, GPS, camera, push, Doze, and OEM background managers on hardware on a schedule
  • • "Your suite passes on simulator but users report overheating — why?" — the simulator emulates no hardware; state plainly which properties each environment can observe
  • • "How would you build a device matrix?" — minimum OS, newest OS, usage telemetry, low-end hardware, one non-Pixel vendor; justify every row with a defect class it catches
  • • "How do you test app updates?" — install previous release, create data, `adb install -r` the new build, assert session/state/migration; repeat from every supported release

Cheat Sheet

Mobile Testing Approaches

Environment choice: emulator = breadth of logic on every PR · physical device = hardware truth (battery, thermal, GPS, radio, Doze, OEM kill lists) nightly · iOS simulator shares the Mac's hardware, so it measures no hardware.

emulator -avd ci_pixel7 -no-window -no-audio -gpu swiftshader_indirect &
adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 2; done'
adb install -r app-debug.apk

Lifecycle suite:

Group Key command / action Catches
upgrade adb install -r old.apk → create data → adb install -r new.apk data loss, failed migrations
permissions pm grant / pm revoke / appops set ... deny crash on denial, dead-end UI
interruption adb emu gsm call, airplane-mode, am kill, rotation, notification post lost state, stuck spinners
power dumpsys deviceidle force-idle work that never resumes

Matrix axes: minimum OS · newest OS (+beta) · low-RAM and flagship · form factors · at least one non-Pixel OEM — driven by store/analytics distribution numbers.

Report contract (mandatory fields): device model + OS build + app versionName/build + install path + steps + expected/actual + evidence (adb logcat -d, screencap, screenrecord, bugreport).

Continue Learning