Performance and Best Practices
Performance and Best Practices
Section titled “Performance and Best Practices”When to use this reference
Section titled “When to use this reference”Use this file when test runs are slow, flaky, or not scaling in CI, and when you need practical patterns for fast, deterministic Swift Testing suites.
Core principles
Section titled “Core principles”- Prefer deterministic tests over timing-sensitive tests.
- Prefer synchronous verification when asynchronous waiting is not required.
- Keep tests independent so parallel execution remains safe and effective.
- Treat
.serializedas a temporary compromise, not a default architecture.
1) Keep tests synchronous where possible
Section titled “1) Keep tests synchronous where possible”Synchronous tests generally run faster and are easier to reason about.
import Testing
struct PriceCalculator { static func total(_ subtotal: Int, discount: Int) -> Int { subtotal - discount }}
@Test func totalCalculation() { #expect(PriceCalculator.total(100, discount: 20) == 80)}Avoid introducing async or sleeps for purely synchronous logic.
// Avoid:// @Test func totalCalculation() async {// try await Task.sleep(nanoseconds: 100_000_000)// #expect(...)// }2) Avoid unnecessary main-actor isolation
Section titled “2) Avoid unnecessary main-actor isolation”@MainActor can reduce useful parallelization and should be used only when code truly needs main-thread isolation.
import Testing
// Good: non-UI logic stays non-main-actor.@Test func parserIsStable() { #expect("A,B,C".split(separator: ",").count == 3)}
// Use @MainActor only for UI/main-thread sensitive code.@MainActor@Test func viewModelMutation() { #expect(true)}3) Remove shared mutable state
Section titled “3) Remove shared mutable state”Shared mutable state is a major source of flakiness and parallel failures.
import Testing
enum Globals { static var token: String?}
// Flaky pattern:@Test func writeToken() { Globals.token = "abc" #expect(Globals.token == "abc")}
@Test func expectsNoToken() { #expect(Globals.token == nil)}Better: create isolated state per test.
import Testing
struct SessionState { var token: String?}
@Test func isolatedTokenState() { var state = SessionState() state.token = "abc" #expect(state.token == "abc")}4) Prefer in-memory dependencies for the fast path
Section titled “4) Prefer in-memory dependencies for the fast path”Use fakes/in-memory repositories for high-volume test runs; reserve real integration dependencies for dedicated plans.
import Testing
protocol CacheStore { func put(key: String, value: String) func get(key: String) -> String?}
final class InMemoryCacheStore: CacheStore { private var values: [String: String] = [:] func put(key: String, value: String) { values[key] = value } func get(key: String) -> String? { values[key] }}
@Test func cacheRoundTrip() { let cache = InMemoryCacheStore() cache.put(key: "user", value: "42") #expect(cache.get(key: "user") == "42")}5) Use parameterized tests to reduce overhead and improve diagnostics
Section titled “5) Use parameterized tests to reduce overhead and improve diagnostics”import Testing
func isValidPort(_ value: Int) -> Bool { (1...65535).contains(value) }
@Test(arguments: [1, 80, 443, 65535])func validPorts(_ port: Int) { #expect(isValidPort(port))}This reduces duplicated setup code and gives argument-level failure visibility.
6) Keep setup cheap and scoped
Section titled “6) Keep setup cheap and scoped”- Build expensive fixtures only when needed.
- Prefer per-suite immutable setup for shared readonly data.
- Avoid network/file-system setup in unit tests unless behavior depends on it.
import Testing
struct CurrencyTests { let rates: [String: Double]
init() { rates = ["USD": 1.0, "EUR": 0.92] }
@Test func hasEURRate() { #expect(rates["EUR"] != nil) }}7) Use .serialized narrowly
Section titled “7) Use .serialized narrowly”import Testing
@Suite(.serialized)struct TemporarySerialDBTests { @Test func migrationA() async throws { #expect(true) } @Test func migrationB() async throws { #expect(true) }}Add TODO context and remove once dependencies are isolated.
8) Flakiness reduction checklist
Section titled “8) Flakiness reduction checklist”- No reliance on execution order.
- No shared mutable globals/singletons without reset.
- No arbitrary sleeps as synchronization.
- No hidden external dependencies in unit tests.
- Deterministic fixtures and stable clocks/random sources.
- Explicit known-issue wrappers for temporary failures.
Quick do / don’t
Section titled “Quick do / don’t”- Do optimize for determinism first, then speed.
- Do keep most tests parallel-safe and dependency-light.
- Don’t treat test slowness as only a hardware problem.
- Don’t move everything to
@MainActoror.serializedto silence flakiness.