Question 4
Maintaining a healthy test suite
As a codebase grows, test suites often become slow and brittle. How do you keep tests fast, reliable, and valuable over time?
Follow-ups
- How do you handle flaky tests in CI?
Answer outline
Keep the test pyramid shape and treat the suite like product code. Name tests after behavior, delete duplicates, and move repeated setup into shared factories.
A small factory keeps model construction in one place with sensible defaults, so each test passes only the fields that matter:
struct User: Equatable {
let id: String
let name: String
let email: String
}
enum UserFactory {
static func make(
id: String = "u1",
name: String = "Ada Lovelace",
email: String = "ada@example.com"
) -> User {
User(id: id, name: name, email: email)
}
}
// Examples: override just what you assert on
let guest = UserFactory.make(id: "guest-42")
let verified = UserFactory.make(name: "Grace Hopper", email: "grace@example.org")
Slow suites usually come from too much I/O, global state, or fixtures far larger than the test needs. Partition by speed: unit tests on every push, integration tests in a separate job, UI and device tests nightly. Parallelize by giving each worker its own derived data and temp directories.
Brittleness often comes from live backend data. Real servers change, go down, or return different responses, so tests fail for reasons unrelated to your code. Stub or fake external dependencies so each test asserts your logic rather than the state of a backend.
Principles
- Track test duration per target and fail the CI build when a target exceeds its time budget.
- Quarantine a flaky test, then fix the root cause or delete it rather than skipping it forever.
- Build test data through shared factories with sensible defaults so setup is never duplicated.
- Keep real networking out of unit tests, and refresh or delete recorded server responses once they drift from the live API.
- Write tests that read like bug reports, with a clear name and one focused assertion.
Follow-up angles
- When a test flakes in CI, quarantine it the same day so it stops blocking merges. Then loop it locally until the shared state or timing behind it shows up, and delete it if nobody can make it deterministic.



