Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions SOURCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ only on request.

- Local: `skills/swarm-and-push` (SreeStack).

## swift-testing-modernization

- Local: `skills/swift-testing-modernization` (SreeStack).

## liquid-glass

- [haider-nawaz/liquid-glass-skill / plugins/liquid-glass/skills/liquid-glass](https://github.com/haider-nawaz/liquid-glass-skill/tree/2c1b2789c30dc2c9208f3b9a3811d42480714577/plugins/liquid-glass/skills/liquid-glass) — commit `2c1b2789c30dc2c9208f3b9a3811d42480714577`.
Expand Down
46 changes: 46 additions & 0 deletions skills/swift-testing-modernization/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
name: swift-testing-modernization
description: Modernize existing Swift unit or integration tests, including incremental migration from XCTest to Swift Testing. Use for test-suite migration or restructuring; use XCTest workflows for UI and performance tests.
---

# Swift Testing Modernization

Modernize tests without changing the behavior they specify. Preserve coverage, failure semantics, platform assumptions, and the command the project uses to run the suite.

Use the project's supported Xcode and Swift versions as the API boundary. Swift Testing ships with Xcode 16 and Swift 6 toolchains; prefer the built-in module rather than adding the `swift-testing` package unless the project already has a reason to use it ([distribution guidance](https://github.com/swiftlang/swift-testing/blob/main/Documentation/Distributions.md)).

Check each migration API against that boundary. Swift Testing gains APIs between toolchain releases; when the supported version lacks a semantics-preserving replacement, keep that test or construct in XCTest as a deliberate holdout.

## Establish the baseline

1. Find the test targets, test plans, package manifests, CI commands, and supported toolchain versions.
2. Classify the affected tests as XCTest unit or integration tests, Swift Testing tests, performance tests, or XCTest UI automation.
3. Run the narrowest existing command that covers the slice. Record selected tests, failures, skips, and known flaky behavior. If the baseline cannot run, identify that constraint before editing.
4. Choose a small, coherent slice. XCTest and Swift Testing can coexist in one target and source file, so migration need not be all at once ([Apple migration guide](https://developer.apple.com/documentation/testing/migratingfromxctest)).

Keep UI automation and performance tests in XCTest. Swift Testing is intended for unit and integration tests that call code directly; Apple still directs UI and performance testing through XCTest ([Xcode testing systems](https://developer.apple.com/documentation/xcode/adding-tests-to-your-xcode-project)).

## Choose the work path

- For XCTest unit or integration tests, read [XCTest migration](references/xctest-migration.md) before editing. Account for every setup, teardown, assertion, skip, expected failure, asynchronous wait, attachment, and execution-order dependency in the selected slice.
- For tests already using Swift Testing, read [suite modernization](references/suite-modernization.md). Apply only changes that improve diagnostics, isolation, selection, or maintenance for the current suite.
- If the user asks for test-first or red-green work, use the `tdd` skill. For ordinary coverage additions, use the project's test workflow. This skill handles the structure and semantics of an existing suite.
- If the request is only to diagnose a failing test, use the `diagnosing-bugs` skill. Return here only when diagnosis identifies a bounded migration or suite-structure change.

## Make a behavior-preserving slice

Trace each old test to its replacement. Preserve what makes execution stop, what may continue after a failed check, which actor owns thread-sensitive work, and whether cases share mutable state.

Prefer isolated fixtures and parallel-safe tests. Add serialization only when the selected slice still depends on shared state that cannot reasonably be removed. A serialized suite prevents its descendants from running concurrently; it does not guarantee their sequence or coordinate them with unrelated tests ([parallelization rules](https://developer.apple.com/documentation/testing/parallelization)).

Keep names and comments that explain business behavior. Use display names, tags, bug links, conditions, known issues, and attachments when they improve test selection or failure diagnosis. Avoid broad cleanup outside the selected slice.

## Verify the migration

1. Build and run the same narrow command used for the baseline.
2. Compare discovery and outcomes: the intended cases still run, expected skips and known issues remain visible, and no assertion became weaker or non-fatal by accident.
3. Run the containing target or test plan. Exercise parallel execution when the migration changed fixture ownership, global state, actor isolation, or serialization.
4. Inspect at least one representative failure when assertion or async-event mechanics changed. Confirm the failure points to the useful expression or event and stops or continues at the intended place.
5. Report the migrated slice, commands and outcomes, deliberate XCTest holdouts, and any baseline failure that prevented a comparison.

The slice is complete when the same behavior is covered, the intended tests are discovered in the project's normal runner, and the broader affected target passes or its pre-existing failures are clearly separated.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Suite Modernization

Use these changes selectively for existing Swift Testing suites. Keep a change when it improves a real maintenance, selection, isolation, or diagnostic problem.

## Parameterize repeated behavior

Replace repeated tests or an opaque loop with `@Test(arguments:)` when one test body describes every case. Each argument becomes its own reported case, which makes the failing input visible ([parameterized tests](https://developer.apple.com/documentation/testing/parameterizedtesting)).

- Use one collection for one varying input.
- Two collections produce their Cartesian product. Use `zip` when inputs are paired.
- Keep separate test functions when cases have materially different setup, behavior, or expected diagnostics.
- Prefer stable, encodable argument types when developers need to rerun a selected case.

## Tighten suite structure

- Put related tests in suites that match the behavior they specify. Nest suites only when inherited traits or navigation improve.
- Move repeated fixture creation into a suite initializer while keeping each test's instance independent.
- Replace shared mutable state with per-test values or concurrency-safe collaborators. Use `.serialized` only for a remaining suite-local dependency.
- Apply tags, conditions, time limits, and bug links at the narrowest level that accurately describes the affected tests. Suite traits are inherited by contained tests ([traits](https://developer.apple.com/documentation/testing/traits)).

## Improve checks and diagnostics

- Use `#require` for prerequisites and unwraps; use `#expect` for independent outcomes that can all be evaluated.
- Prefer expressions that show the relationship directly so macro diagnostics can display the relevant values.
- Use `withKnownIssue` only for a tracked, understood defect. Keep its matching and condition narrow so new failures remain visible.
- On Swift 6.2 / Xcode 26 or later, record an attachment when a value, file, image, or structured artifact materially shortens diagnosis ([availability](https://github.com/swiftlang/swift-testing/blob/main/Sources/Testing/Attachments/Attachment.swift)). Avoid routine attachments that add noise or storage without explaining failures.
- Use `confirmation` only for events delivered before its closure returns. Await ordinary async results directly.

## Check the result

Run the affected cases individually and through their containing suite. If parameterization, fixtures, or parallel safety changed, compare case discovery and run the suite repeatedly or under the project's stress method. A modernization is useful only if it preserves coverage and yields clearer selection, isolation, or failures.
79 changes: 79 additions & 0 deletions skills/swift-testing-modernization/references/xctest-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# XCTest Migration

Use this reference for XCTest unit and integration tests. Migrate one coherent class or helper boundary at a time; mixed XCTest and Swift Testing code is supported during the transition ([Apple migration guide](https://developer.apple.com/documentation/testing/migratingfromxctest)).

## Suite and fixture lifecycle

- Replace `import XCTest` with `import Testing` only after the file no longer needs XCTest. Keep both imports while content is mixed.
- Remove `XCTestCase` inheritance. Prefer a `struct` suite. Use an `actor` or `final class` when reference identity or teardown requires it.
- Move per-test setup into stored-property defaults or `init()`. The initializer may be `async throws`. Swift Testing creates a distinct suite instance for every instance test function ([suite lifecycle](https://developer.apple.com/documentation/testing/organizingtests)).
- Move synchronous teardown into `deinit` on an actor or final class. Treat `addTeardownBlock` separately: preserve cleanup after success, ordinary failure, and thrown fail-stop; last-in, first-out order; actor isolation; and async or throwing cleanup failures as test issues. A synchronous nonthrowing `defer` works only when it is registered before any exit and reproduces those semantics. For async or throwing cleanup, use a scoped helper or `do`/`catch` structure that runs cleanup on success and error while preserving the original outcome. Keep the test in XCTest when the lifecycle cannot be reproduced safely.
- Replace implicitly unwrapped fixture properties with initialized nonoptional values where the old setup guaranteed a value.
- Add `@MainActor` only where the old synchronous XCTest method actually depended on main-actor execution. Swift Testing otherwise runs tests on arbitrary tasks.

Review imports after removing XCTest. Add direct imports for APIs the file uses instead of relying on modules that XCTest happened to re-export.

### Toolchain compatibility

Check the project's compiler and Xcode versions before choosing a replacement. Current minimums for migration features that arrived after Swift Testing's first release are:

| Feature | Minimum built-in toolchain | Older-toolchain path |
| --- | --- | --- |
| Range-valued `confirmation(expectedCount:)` and the error returned by `#expect(throws:)` | Swift 6.1 / Xcode 16.3 | Use an exact confirmation count where equivalent. Use an exact error or matcher check that the compiler supports. |
| `Attachment` and `Attachable` | Swift 6.2 / Xcode 26 | Keep the XCTest attachment and its test in XCTest when the evidence must be preserved. |
| `Test.cancel()` | Swift 6.3 / Xcode 26.4 | Express a pre-run condition as a trait, or keep the dynamically skipped test in XCTest. |

These minimums come from the Swift Testing source documentation for [confirmations](https://github.com/swiftlang/swift-testing/blob/main/Sources/Testing/Issues/Confirmation.swift), [attachments](https://github.com/swiftlang/swift-testing/blob/main/Sources/Testing/Attachments/Attachment.swift), and [test cancellation](https://github.com/swiftlang/swift-testing/blob/main/Sources/Testing/Test%2BCancellation.swift). Do not raise the project's toolchain merely to complete a migration.

## Test declarations and checks

Replace the `test` naming convention with `@Test`. A containing type is already a suite; add `@Suite` when it needs a display name or suite-level traits.

Use the expression that states the relationship directly:

| XCTest | Swift Testing |
| --- | --- |
| `XCTAssert(x)`, `XCTAssertTrue(x)` | `#expect(x)` |
| `XCTAssertFalse(x)` | `#expect(!x)` |
| `XCTAssertNil(x)` | `#expect(x == nil)` |
| `XCTAssertNotNil(x)` | `#expect(x != nil)` |
| `XCTAssertEqual(x, y)` | `#expect(x == y)` |
| `XCTAssertNotEqual(x, y)` | `#expect(x != y)` |
| identity and ordering assertions | `#expect` with `===`, `!==`, `<`, `<=`, `>`, or `>=` |
| `try XCTUnwrap(x)` | `try #require(x)` |
| `XCTAssertThrowsError(try f())` | `#expect(throws: (any Error).self) { try f() }` |
| `XCTAssertNoThrow(try f())` | `#expect(throws: Never.self) { try f() }` |
| unconditional `XCTFail` | `Issue.record` |

Prefer an exact error value with `#expect(throws:)` when the error is `Equatable`. If the old closure inspected the thrown error, capture the result of `#expect(throws:)` and keep equivalent checks.

There is no direct Swift Testing equivalent for `XCTAssertEqual(_:_:accuracy:)`. Use the project's numeric comparison facility; Apple's guide points to `isApproximatelyEqual()` from Swift Numerics.

### Preserve failure control

`#expect` records an issue and continues. `try #require` records an issue and stops the current test by throwing. Preserve this distinction:

- Convert `XCTUnwrap` and prerequisite assertions to `try #require` when later code is invalid without the value or condition.
- When an XCTest method sets `continueAfterFailure = false`, use `try #require` for checks that previously stopped execution. If setup set it for the whole class, audit every method in the migrated suite.
- Override the table's continuing forms wherever fail-stop behavior applied. Convert `XCTFail("…")` to `try #require(false, "…")`. For `XCTAssertNoThrow`, call `try f()` directly so an error stops the test. When its custom message carries useful context, catch the error and terminate with `try #require(false, "context: \(error)")`. Add `throws` to the migrated test as needed.
- Do not mechanically promote every assertion to `#require`; doing so can hide independent failures that XCTest previously reported together.

## Asynchronous behavior

Prefer structured concurrency. Await an async API directly. When bridging a callback that returns one result, preserve the XCTest wait's deadline, timeout outcome, late-callback behavior, and cancellation behavior. Use the project's bounded async helper when it has one. Keep the test in XCTest when no safe bounded bridge exists; a bare checked continuation can suspend forever if the callback never arrives.

Use `confirmation` for asynchronously delivered events whose producer completes within the confirmation closure. A confirmation does not wait after its closure returns; it records an issue if the expected count was not reached by then ([testing asynchronous code](https://developer.apple.com/documentation/testing/testing-asynchronous-code)). Preserve inverted and repeated-event expectations with an appropriate expected count or range.

Do not translate `XCTestExpectation` to `confirmation` solely by syntax. First determine what caused the old wait to finish and whether the migrated closure keeps that work in scope. Exercise the missing and late callback paths when the migration changes the waiting mechanism.

## Skips, known failures, and evidence

- Express a pre-run condition with `.enabled(if:)` or `.disabled(if:)`. Put `@available` on individual `@Test` functions for platform or language availability; containing suite types must remain universally available. Use `try Test.cancel("reason")` when the reason arises during a test, and allow that test to throw.
- Map `XCTExpectFailure` with a closure to the same `withKnownIssue` scope. The no-closure form affects the rest of an XCTest method and has no direct equivalent; wrap the intended remainder of the migrated test in `withKnownIssue`. Preserve its condition, issue matching, and strictness. Mark an issue intermittent only when the original test allowed intermittent success ([Apple migration guide](https://developer.apple.com/documentation/testing/migratingfromxctest)).
- Replace `XCTAttachment` with `Attachment.record` when the evidence remains useful. Confirm the value conforms to `Attachable` and that the runner keeps attachments where the project expects them.

## Parallel execution

XCTest runs tests in a suite sequentially by default; Swift Testing runs tests in parallel by default. Remove shared mutable fixtures when practical. When behavior genuinely depends on suite-local shared state, annotate the suite with `@Suite(.serialized)` and document the dependency that justifies it. Serialization prevents overlap among the suite's descendants but does not promise a particular sequence. Remove order dependencies, combine inherently ordered stages into one test, or keep them in XCTest.

Serialization applies recursively within that suite, but does not coordinate it with unrelated suites. Give tests unique resources, place related suites under one serialized ancestor when that structure is accurate, or use a shared synchronization owner.
Loading