Swift Testing Agent Skill

repository·main·Indexed 19 days ago

https://github.com/avdlee/swift-testing-agent-skill

A specialized knowledge base for AI coding agents providing expert guidance on modern Swift Testing APIs, XCTest migration, and best practices. It covers test architecture, parameterized testing, asynchronous testing with async/await and confirmations, and reliability patterns to prevent flaky tests in parallel execution environments.

Tokens
12.5K
Snippets
40
Records
61
Agent score
64%

What's inside swift-testing-agent-skill

  1. Capabilities of the Swift Testing Agent Skill

    main

    This skill provides expert guidance for Swift Testing across several domains:

    Test Architecture

    • Guidance on using suites, traits, tags, and display names.
    • Converting repetitive tests into parameterized tests.
    • Applying parallel-safe patterns and managing .serialized usage.
    • Setting up tag-driven test plan filtering.

    Writing Tests

    • Effective use of #expect for rich diagnostics.
    • Using #require for prerequisite flow and safe unwrapping.
    • Modeling thrown-error expectations.
    • Improving output readability with descriptions.

    XCTest Migration

    • Strategies for the coexistence of Swift Testing and XCTest in a single target.
    • Mapping XCTAssert* patterns to Swift Testing macros.
    • Identifying when to keep XCTest (e.g., XCUIApplication, XCTMetric, or Objective-C tests).

    Reliability and Performance

    • Removing inter-test dependencies caused by randomized, parallel execution.
    • Stabilizing server-side tests via repository isolation.
    • Bridging callback APIs to async/await for deterministic testing.
    • Reducing CI noise using trait metadata.
  2. Swift Testing Reference Index

    main

    This index provides a roadmap to the specialized knowledge available for using the Swift Testing Agent Skill. Use these topics to master different aspects of the Swift Testing framework:

    • Core Fundamentals: Learn about @Test, test suites, structure, naming, and baseline patterns.
    • Assertions and Expectations: Master #expect, #require, throw validation, and improving failure readability.
    • Traits and Tags: Use traits, tags, bug linking, conditions, and test-plan filtering.
    • Parameterized Testing: Implement single/multi-argument parameterization, zip, and scaling strategies.
    • Execution Control: Understand parallelization, isolation, .serialized execution, and random order.
    • Performance and Reliability: Learn about test speed, determinism, flaky-test prevention, and parallel-safe defaults.
    • Asynchronous Testing: Work with async/await, callback bridging, and event-stream verification.
    • Migration: Follow the pragmatic workflow for migrating from XCTest to Swift Testing.
    • Xcode Workflows: Optimize your use of the Xcode navigator, reports, insights, and diagnostics.
  3. Use the Swift Testing Agent Skill

    main

    The swift-testing-expert skill provides guidance for writing, reviewing, migrating, and debugging Swift tests using modern Swift Testing APIs. It is designed for developers working on Apple-platform or Swift server projects who want to prioritize readable tests, robust parallel execution, and clear diagnostics.

    Core Capabilities

    • Test Architecture: Guidance on suite organization and building blocks.
    • Assertions: Best practices for using #expect and #require macros.
    • Metadata: Using traits and tags for behavior and filtering.
    • Advanced Testing: Implementing parameterized tests and managing async/await patterns.
    • Migration: Incremental workflows for moving from XCTest to Swift Testing.
    • Performance: Strategies for parallel execution, isolation, and preventing flakiness.
  4. Handle shared resources in parallel tests

    main

    If your tests must interact with shared resources like a database, file system, or external service, use one of the following strategies:

    1. Isolate backing state: Ensure each test uses a unique identifier or separate instance of the resource.
    2. Use in-memory substitutes: Use fakes or in-memory versions of the resource for faster, isolated testing.
    3. Selective serialization: Create a separate serial test plan specifically for the integration path that requires sequential access.
  5. Enforce suite initialization requirements

    main

    If a suite contains instance test methods, the suite type must have a callable zero-argument initializer. This can be an implicit initializer or an explicit one that provides default values for its properties.

    If you cannot provide a zero-argument initializer, you must either:

    1. Convert the tests to static/global functions.
    2. Refactor the suite state to allow for zero-argument initialization.
    import Testing
    
    @Suite
    struct SessionTests {
     let config: URLSessionConfiguration
    
     // Valid: callable with zero args due to default value.
     init(config: URLSessionConfiguration = .ephemeral) {
     self.config = config
     }
    
     @Test func usesEphemeralByDefault() {
     #expect(config == .ephemeral)
     }
    }
  6. Pair inputs using zip, tuples, or dictionaries

    main

    When you need input A to correspond specifically to input B (rather than testing every combination), use one of the following patterns:

    1. zip (Use with caution)

    Use zip(collectionA, collectionB) when you want to pair elements by index. Pitfalls:

    • Silent Truncation: zip stops at the shorter collection. If lengths differ, extra elements are silently ignored without error.
    • Fragility: Using zip(Enum.allCases, Enum.allCases) is dangerous because reordering enum cases will silently misalign your pairs.

    Co-locate pairs in a single array of tuples. This is the safest method because it is impossible to misalign inputs, and adding a new case requires adding the corresponding expected value immediately.

    3. Dictionary Arguments

    Use a dictionary for clear, self-documenting mappings. This requires the keys to be Hashable.

    4. Fixed-size zip with InlineArray (Swift 6.2+)

    For advanced users, you can implement a custom helper using InlineArray to enforce equal-length arrays at compile time.

    import Testing
    
    // ✅ Recommended: Array of Tuples
    @Test(arguments: [
     (Ingredient.rice, Dish.onigiri),
     (.potato, .fries),
     (.egg, .omelette)
    ])
    func cook(_ ingredient: Ingredient, into dish: Dish) {
     #expect(cook(ingredient) == dish)
    }
    
    // ✅ Clear: Dictionary
    @Test(arguments: [
     Ingredient.rice: Dish.onigiri,
     .potato: .fries,
     .egg: .omelette
    ])
    func cook(_ ingredient: Ingredient, into dish: Dish) {
     #expect(cook(ingredient) == dish)
    }
    
    // ⚠️ Risky: zip (can truncate or misalign)
    @Test(arguments: zip([Tier.basic, .premium], [3, 10]))
    func freeTryLimits(_ tier: Tier, expected: Int) {
     #expect(freeTries(for: tier) == expected)
    }
  7. Migrate test setup and teardown to Swift Testing suites

    main

    Swift Testing uses a suite model (structs, actors, or classes) rather than requiring XCTestCase.

    • Setup: Instead of setUp(), use the init() method of your suite (struct/class/actor) to initialize shared state.
    • Teardown: Use deinit when using class or actor-based suites.
    • Concurrency: Note that XCTest sync tests default to the @MainActor, whereas Swift Testing runs on arbitrary tasks unless you explicitly isolate them (e.g., using @MainActor).
    import Testing
    
    struct SessionTests {
     let session: Session
    
     init() {
     self.session = Session(environment: .test)
     }
    
     @Test func startsDisconnected() {
     #expect(session.isConnected == false)
     }
    }
  8. Swift Testing building blocks

    main

    To use Swift Testing, follow these core principles:

    • Imports: Only import Testing in your test targets; do not import it in your application code.
    • Test Declaration: Use the @Test attribute to explicitly declare tests. These can be global functions or methods within a type.
    • Suites: Use struct, actor, or class to group related tests.
      • Recommendation: Prefer struct for suites to leverage value semantics and prevent accidental state sharing between tests.
    • Suite Metadata: Use the @Suite attribute when you need to add suite-level traits or custom display names.
    • Hierarchy: Use nested suites to reflect feature groupings and improve test discoverability.
  9. Filter and group tests using tags

    main

    Instead of using fragile test-name patterns, use tags to filter and group tests in the Xcode navigator. This enables focused development loops and stable cross-suite inspection.

    Suggested Tag Conventions:

    • core: Always-on, fast checks.
    • integration: Tests covering external dependencies.
    • regression: Tests used to lock in bug fixes.
    • flaky: Tests temporarily quarantined while being fixed.
  10. Control test execution with Traits

    main

    Swift Testing uses Traits to modify test behavior, provide metadata, or control execution conditions. Traits can be categorized into three types:

    1. Informational: Used for display names, linking bug reports, or adding metadata (e.g., .bug("URL")).
    2. Conditional: Used to enable or disable tests based on runtime conditions or availability (e.g., .enabled(if:), .disabled("reason")).
    3. Behavioral: Used to modify how the test runs (e.g., .timeLimit(...), .serialized).

    Always include an actionable reason when using .disabled("reason") to assist in CI/test reporting.

    import Testing
    
    @Test("Uploads complete quickly", .timeLimit(.seconds(10)))
    func uploadWithinTimeLimit() async throws {
     #expect(true)
    }
    
    @Test(.disabled("Flaky on CI while investigating issue"), .bug("https://example.com/issues/12"))
    func temporaryDisabledTest() {
     #expect(true)
    }