- Never force unwrap (
!) or force cast (as!) — useif let,guard let, oras?instead
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActoris set on both app and test targets — don't add explicit@MainActorto production code or test structs/functions, it's already the default- Always use Swift Concurrency (actors, async/await, Sendable) — avoid locks, mutexes, or other low-level synchronization
- When adding unit tests, do not modify production code just to accommodate a test. If a genuine bug is found, fix it in a separate commit with its own justification.
- Place
@Testand@Suiteannotations on the line above the declaration, not inline - Use
// whenand// thencomment blocks; skip// given(assumed from context) - When XCTest assertions have message strings, preserve them as
#expectmessages, not code comments (e.g.#expect(x == false, "reason")) - Avoid
#requireonBool?— it's ambiguous; use#expect(x == true)instead - Capture a baseline of compiler warnings before each phase, then verify no new warnings after. Use this command and compare the output before/after:
xcodebuild clean build-for-testing -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" 2>&1 | grep -i "warning:" | grep -v "xcodebuild: WARNING" - Network test suites use
.serializedtrait.MockURLProtocoluses a simple single static handler — no per-session registry needed. - Avoid snake_case in test names (e.g.,
generateDisplayName_bundleIdentifier). If a name is getting long, use a Trait with a sentence-style description instead. - For complex tests, use a descriptive
@Test("...")trait that explains the scenario and expected outcome so the test is understandable without reading the body. - Use parameterized tests with Traits where it reduces duplication; 1–2 args is ideal, max 3
@Test("Service key round-trip preserves value", arguments: [ ("BluetoothAlways", "Allow"), ("SystemPolicyAppBundles", "Deny") ]) func serviceKeyRoundTrip(serviceKey: String, value: String) async { … }
- Beyond 3 params: create separate tests with some values hard-coded
- Use
deinitas teardown for repeated cleanup across tests in a suite. Useclassfor suites that needdeinit; usestructotherwise. - After adding or refactoring tests, look for duplication or tests that are no longer relevant. Do not add redundant tests.
- UI tests use XCTest (XCUITest), not Swift Testing — the UI test target uses Swift 5 with minimal concurrency checking
- Prefer multiple assertions per test to minimize app launches. Each test method relaunches the app, which is expensive. Group related checks (e.g., verify all buttons exist in one test) rather than one assertion per test.
- Do not use
// when/// thencomment blocks in UI tests — they add noise without clarity in assertion-heavy tests - Use accessibility identifiers set in
setupAccessibilityIdentifiers()to locate UI elements - The
-UITestModelaunch argument triggers test-specific setup (e.g., loading a test profile)
- Service keys (popup rows) in the main window must appear in alphabetical order.
- Avoid "new" in test or function names — it becomes stale quickly. Describe the behavior, not the novelty.
- Do not stage or commit changes in terminal sessions