用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/AutisticAF/claude-code-apple-dev-plugin --skill swift-testing命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Generate compelling App Store descriptions that convert browsers into users. Use when writing or optimizing App Store product descriptions.
Apple Search Ads campaign strategy for indie developers — paid acquisition, keyword bidding, budget planning, and ROAS optimization. Use when user asks about running ads, paid user acquisition, or Apple Search Ads campaigns.
Optimize app title, subtitle, and keywords for App Store discoverability. Use when improving ASO or keyword strategy.
基于 SOC 职业分类
正在显示 SKILL.md
| name | swift-testing |
| description | Swift Testing framework API reference — @Test, |
First step: Tell the user: "swift-testing skill loaded."
The Swift Testing framework (import Testing) is Apple's modern test framework introduced in Xcode 16. It replaces XCTest's XCTAssert family with expressive macros (#expect, #require), supports parameterized tests, traits for conditional execution, and organizes tests with @Suite and @Test attributes.
@Test, #expect, #require, @Suite, traits)Do NOT activate for these — use the specialized skill instead:
generators-test-generatortesting-tdd-feature or testing-tdd-bug-fix| Scenario | Use |
|---|---|
| New test target (Xcode 16+) | Swift Testing |
| UI tests (XCUIApplication) | XCTest (Swift Testing does not support UI testing) |
| Performance tests (measure blocks) | XCTest (Swift Testing has no performance API) |
| Objective-C test code | XCTest (Swift Testing is Swift-only) |
| Parameterized / data-driven tests | Swift Testing (first-class support) |
| Existing XCTest target, adding new tests | Either; they coexist in the same target |
| KIF or EarlGrey UI tests | XCTest |
| Requirement | Minimum |
|---|---|
| Import | import Testing |
| Xcode | 16.0+ |
| Swift | 6.0+ |
| iOS deployment target | 16.0+ |
| macOS deployment target | 13.0+ |
| watchOS deployment target | 9.0+ |
| tvOS deployment target | 16.0+ |
| visionOS deployment target | 1.0+ |
Swift Testing ships with the Xcode toolchain, not the OS SDK. Apps targeting iOS 16+ can use it as long as they build with Xcode 16+.
import Testing
@Test("Addition produces correct sum")
func addition() {
let result = 2 + 3
#expect(result == 5)
}
#expect captures the full expression on failure, showing both sides of the comparison automatically. No need for separate assertEqual / assertTrue variants.
#require throws on failure, stopping the test immediately. Use it for preconditions and optional unwrapping.
@Test func loadUser() throws {
let data = try #require(JSONLoader.load("user.json"))
let user = try #require(User(data: data))
#expect(user.name == "Alice")
}
@Suite("Authentication Tests")
struct AuthenticationTests {
let service: AuthService
init() {
// Runs before each @Test (equivalent to setUp)
service = AuthService(store: MockTokenStore())
}
@Test func loginSucceeds() async throws {
let token = try await service.login(user: "admin", password: "secret")
#expect(token.isValid)
}
@Test func loginFailsWithBadPassword() async throws {
await #expect(throws: AuthError.invalidCredentials) {
try await service.login(user: "admin", password: "wrong")
}
}
}
init() replaces XCTest's setUp() -- runs before each test.deinit replaces tearDown() -- runs after each test (requires the suite to be a class).Traits modify test behavior. Pass them as arguments to @Test or @Suite.
@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil))
func onlyOnCI() {
// Runs only in CI
}
@Test(.disabled("Blocked by #1234"))
func brokenFeature() {
// Skipped with a reason
}
@Test(.bug("https://github.com/org/repo/issues/42", "Crash on empty input"))
func emptyInputHandling() {
#expect(Parser.parse("") == .empty)
}
extension Tag {
@Tag static var networking: Self
@Tag static var database: Self
}
@Test(.tags(.networking))
func fetchData() async throws {
let data = try await APIClient.shared.fetch("/items")
#expect(!data.isEmpty)
}
Run only tagged tests from the command line:
swift test --filter .tags:networking
@Test(.timeLimit(.minutes(1)))
func longRunningOperation() async throws {
let result = try await processor.run(largeDataset)
#expect(result.isComplete)
}
@Test(
"Sync engine completes within time limit",
.tags(.networking, .database),
.timeLimit(.minutes(2)),
.enabled(if: FeatureFlags.syncV2Enabled)
)
func syncEngine() async throws {
// ...
}
@Test(arguments: ["hello", "world", "swift"])
func stringIsNotEmpty(_ value: String) {
#expect(!value.isEmpty)
}
@Test(arguments: [1, 2, 3], ["a", "b", "c"])
func pairCombinations(number: Int, letter: String) {
#expect(!letter.isEmpty)
#expect(number > 0)
}
This produces the Cartesian product: (1,"a"), (1,"b"), (1,"c"), (2,"a"), ... (9 total).
@Test(arguments: zip([1, 2, 3], ["one", "two", "three"]))
func zippedPairs(number: Int, name: String) {
#expect(!name.isEmpty)
}
enum Direction: CaseIterable {
case north, south, east, west
}
@Test(arguments: Direction.allCases)
func directionHasOpposite(_ direction: Direction) {
#expect(direction.opposite.opposite == direction)
}
When test arguments are complex types, conform to CustomTestStringConvertible to get readable test names in Xcode's test navigator.
struct UserFixture: CustomTestStringConvertible, Sendable {
let name: String
let age: Int
let expectedTier: MembershipTier
var testDescription: String { "\(name) (age \(age))" }
}
@Test(arguments: [
UserFixture(name: "Teen", age: 15, expectedTier: .junior),
UserFixture(name: "Adult", age: 30, expectedTier: .standard),
UserFixture(name: "Senior", age: 65, expectedTier: .senior),
])
func membershipTier(_ fixture: UserFixture) {
let tier = MembershipTier.for(age: fixture.age)
#expect(tier == fixture.expectedTier)
}
@Test func invalidInputThrows() {
#expect(throws: ValidationError.self) {
try Validator.validate("")
}
}
@Test func invalidInputThrowsSpecificError() {
#expect(throws: ValidationError.emptyField("name")) {
try Validator.validate(field: "name", value: "")
}
}
@Test func errorContainsContext() throws {
let error = try #require(throws: NetworkError.self) {
try client.fetchSync(url: badURL)
}
#expect(error.statusCode == 404)
#expect(error.url == badURL)
}
// #expect does NOT throw -- this naturally fails if an error is thrown:
@Test func validInputSucceeds() throws {
let result = try Validator.validate("hello")
#expect(result.isValid)
}
Async tests work naturally. Mark the test function async and/or throws.
@Test func fetchUserProfile() async throws {
let profile = try await api.fetchProfile(id: "user-1")
#expect(profile.name == "Alice")
}
@Test func notificationPosted() async {
await confirmation("UserDidLogin notification received") { confirm in
let observer = NotificationCenter.default.addObserver(
forName: .userDidLogin, object: nil, queue: .main
) { _ in
confirm()
}
LoginManager.shared.login(user: "test", password: "test")
NotificationCenter.default.removeObserver(observer)
}
}
For multiple expected confirmations:
await confirmation("Progress callbacks", expectedCount: 3) { confirm in
downloader.onProgress = { _ in confirm() }
await downloader.download(url: fileURL)
}
| XCTest | Swift Testing |
|---|---|
XCTAssertTrue(x) | #expect(x) |
XCTAssertFalse(x) | #expect(!x) |
XCTAssertEqual(a, b) | #expect(a == b) |
XCTAssertNotEqual(a, b) | #expect(a != b) |
XCTAssertNil(x) | #expect(x == nil) |
XCTAssertNotNil(x) | #expect(x != nil) or try #require(x) |
XCTAssertGreaterThan(a, b) | #expect(a > b) |
XCTAssertThrowsError(expr) | #expect(throws: SomeError.self) { expr } |
XCTUnwrap(x) | try #require(x) |
XCTFail("msg") | Issue.record("msg") |
| XCTest | Swift Testing |
|---|---|
class FooTests: XCTestCase | @Suite struct FooTests |
func testBar() | @Test func bar() |
override func setUp() | init() |
override func tearDown() | deinit (class suites only) |
XCTestExpectation + wait(for:) | confirmation { } |
addTeardownBlock { } | deinit or explicit cleanup in test |
// ❌ XCTest style
import XCTest
class ParserTests: XCTestCase {
var parser: Parser!
override func setUp() {
super.setUp()
parser = Parser()
}
func testParseValidJSON() throws {
let result = try parser.parse("{\"key\": \"value\"}")
XCTAssertNotNil(result)
XCTAssertEqual(result?["key"] as? String, "value")
}
func testParseInvalidJSONThrows() {
XCTAssertThrowsError(try parser.parse("not json"))
}
}
// ✅ Swift Testing style
import Testing
@Suite struct ParserTests {
let parser = Parser()
@Test func parseValidJSON() throws {
let result = try #require(parser.parse("{\"key\": \"value\"}"))
#expect(result["key"] as? String == "value")
}
@Test func parseInvalidJSONThrows() {
#expect(throws: ParserError.self) {
try parser.parse("not json")
}
}
}
@Test or @Suite in the gutter# Run all tests
swift test
# Filter by test name
swift test --filter "ParserTests"
# Filter by tag
swift test --filter .tags:networking
Swift Testing and XCTest coexist in the same test target. Rules:
XCTestCase in Swift Testing suites.@Test on methods inside an XCTestCase subclass.// File: ParserXCTests.swift -- XCTest
import XCTest
class ParserXCTests: XCTestCase {
func testLegacyBehavior() {
XCTAssertEqual(Parser.legacyMode, true)
}
}
// File: ParserSwiftTests.swift -- Swift Testing
import Testing
@Suite struct ParserSwiftTests {
@Test func modernBehavior() {
#expect(Parser.modernMode == true)
}
}
// ❌ Bad: loses context on failure
#expect(result == true)
#expect(array.count == 3)
// ✅ Good: use the natural expression
#expect(result)
#expect(array.count == 3) // this is fine -- #expect shows both sides
// ❌ Bad: force unwrap crashes the test runner
let user = users.first!
#expect(user.name == "Alice")
// ✅ Good: #require fails gracefully and stops the test
let user = try #require(users.first)
#expect(user.name == "Alice")
// ❌ Bad: unnecessary class when no tearDown is needed
@Suite class CalculatorTests {
@Test func add() { #expect(Calculator.add(2, 3) == 5) }
}
// ✅ Good: struct with init for setup
@Suite struct CalculatorTests {
let calculator = Calculator()
@Test func add() { #expect(calculator.add(2, 3) == 5) }
}
// ❌ Bad: duplicated tests
@Test func parseCSVComma() throws {
let result = try CSVParser.parse("a,b,c")
#expect(result == ["a", "b", "c"])
}
@Test func parseCSVSemicolon() throws {
let result = try CSVParser.parse("a;b;c", delimiter: ";")
#expect(result == ["a", "b", "c"])
}
// ✅ Good: parameterized
struct CSVCase: CustomTestStringConvertible, Sendable {
let input: String
let delimiter: Character
let expected: [String]
var testDescription: String { "delimiter: \(delimiter)" }
}
@Test(arguments: [
CSVCase(input: "a,b,c", delimiter: ",", expected: ["a", "b", "c"]),
CSVCase(input: "a;b;c", delimiter: , expected: [, , ]),
])
( : ) {
result .parse(testCase.input, delimiter: testCase.delimiter)
#expect(result testCase.expected)
}
// ❌ Bad: runtime skip with guard
@Test func networkTest() async throws {
guard ProcessInfo.processInfo.environment["CI"] != nil else { return }
// ...
}
// ✅ Good: trait makes intent clear and shows "skipped" in results
@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil))
func networkTest() async throws {
// ...
}