Question 1

Testing strategy and tradeoffs

How do you decide what to test at the unit, integration, and UI level in a large iOS app?

Answer outline

Start from the cost of failure. Onboarding, sign-in, and in-app purchase are the flows where a bug costs the most, so they earn the deepest coverage. Then push each check down to the cheapest level that can still catch the bug.

Each level catches a different kind of failure:

  1. 1.Unit tests: pure logic and contracts you can isolate, such as parsers, state machines, and pricing rules. They run in milliseconds and point at the exact line that broke.
  2. 2.Integration tests: system boundaries where several real types must agree, such as a repository against a real database or a coordinator resolving a deeplink. Stub the network so they stay deterministic.
  3. 3.UI tests: the slowest and flakiest level, so save them for a few critical journeys and for gaps the lower layers cannot cover. Find elements by accessibility identifier and run them against seeded, deterministic data.

The test pyramid follows from the cost of running each level. Most checks belong at the bottom, and a UI test that repeats what a unit test already proves only adds minutes to every run.

Principles

  • Unit test anything deterministic without I/O, such as algorithms, state machines, and parsers.
  • Write an integration test where real types meet, such as a repository against its database.
  • Keep UI tests to a few stable flows, find elements by accessibility identifier, and seed the data they run on.
  • Spend the deepest tests where a bug costs the most, and push everything else to the cheapest level that catches it.