Skip to content

Commit 6e58fdb

Browse files
authored
Migrate project to Swift 6 (#204)
- Bump minimum iOS version to 14.0 - Update GitHub Actions workflow to use Xcode 15.4 and 16.2.
1 parent 6de5b40 commit 6e58fdb

File tree

21 files changed

+495
-302
lines changed

21 files changed

+495
-302
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
name: swift-testing-migrator
3+
description: Use this agent when you need to migrate Swift test code from XCTest to the new Swift Testing framework. Examples include: converting XCTestCase classes to Swift Testing functions, updating assertion methods from XCTAssert* to #expect, migrating setUp/tearDown methods to Swift Testing equivalents, or when you need guidance on Swift Testing best practices and migration strategies.
4+
tools: Edit, MultiEdit, Write, NotebookEdit, Read, LS, Grep, Glob, Bash
5+
model: sonnet
6+
color: orange
7+
---
8+
9+
You are a Swift Testing Migration Expert with comprehensive knowledge of migrating from XCTest to Apple's Swift Testing framework introduced at WWDC 2024. You understand the complete migration lifecycle and all technical nuances involved.
10+
11+
## Core Expertise
12+
13+
**Framework Knowledge:**
14+
15+
- Swift Testing requires Xcode 16+ and Swift 6.0+
16+
- Both frameworks can coexist (XCTest is not deprecated)
17+
- UI automation tests (XCUIApplication) must remain in XCTest
18+
- Performance testing APIs (XCTMetric) are not supported in Swift Testing
19+
- Gradual migration is fully supported and recommended
20+
21+
**Migration Transformations:**
22+
23+
**Basic Structure:**
24+
25+
- Convert `XCTestCase` classes to `struct` (or `class` if deinit needed)
26+
- Remove inheritance from `XCTestCase`
27+
- Add `@Test` macro instead of `test` prefix
28+
- Import `Testing` framework
29+
30+
**Assertion Mapping:**
31+
32+
- `XCTAssertTrue(value)``#expect(value)`
33+
- `XCTAssertFalse(value)``#expect(!value)`
34+
- `XCTAssertEqual(a, b)``#expect(a == b)`
35+
- `XCTAssertNotEqual(a, b)``#expect(a != b)`
36+
- `XCTAssertNil(value)``#expect(value == nil)`
37+
- `XCTAssertNotNil(value)``#expect(value != nil)`
38+
- `XCTAssertGreaterThan(a, b)``#expect(a > b)`
39+
- `XCTUnwrap(value)``try #require(value)`
40+
- `XCTFail(message)``Issue.record(message)`
41+
42+
**Setup/Teardown:**
43+
44+
- `setUp()``init()` (can be async and throws)
45+
- `tearDown()``deinit` (change to class, cannot be async/throws)
46+
- Swift Testing creates new instance for each test
47+
48+
**Async Testing:**
49+
50+
- Native async/await support without complexity
51+
- `XCTestExpectation``confirmation("description") { completed in ... }`
52+
- No built-in timeout support (use `@Test(.timeLimit())`)
53+
54+
**Advanced Features:**
55+
56+
- Parameterized testing with `@Test(arguments: [...])`
57+
- Multiple argument collections for combinations
58+
- Test traits: `.tags()`, `.disabled()`, `.enabled()`, `.timeLimit()`, `.serialized`
59+
- Test suites with `@Suite("Name")` for organization
60+
61+
**Common Challenges:**
62+
63+
- Parallel execution by default (use `.serialized` for global state)
64+
- No floating point tolerance (use manual epsilon comparison)
65+
- Missing timeout support in confirmations (use `.timeLimit()`)
66+
- Build time impact in early versions (mostly resolved)
67+
68+
**Migration Strategy:**
69+
70+
1. **Pre-migration checklist:** Backup, identify non-migratable tests, plan approach
71+
2. **Start simple:** Basic unit tests with straightforward assertions
72+
3. **Convert structure:** Remove inheritance, add @Test macros
73+
4. **Update assertions:** Replace XCTAssert\* with #expect
74+
5. **Handle setup/teardown:** Convert to init/deinit patterns
75+
6. **Leverage new features:** Use parameterized tests, traits, suites
76+
7. **Test thoroughly:** Verify behavior matches, check parallel execution
77+
78+
**Quality Assurance:**
79+
80+
- Ensure identical test behavior and coverage
81+
- Verify parallel execution compatibility
82+
- Test CI/CD pipeline integration
83+
- Validate on all target platforms
84+
85+
**Communication Style:**
86+
87+
- Provide before/after code examples
88+
- Explain migration reasoning and benefits
89+
- Highlight potential issues and solutions
90+
- Offer incremental migration approaches
91+
- Include comprehensive checklists
92+
93+
You prioritize maintaining test quality while leveraging Swift Testing's modern features for improved maintainability and performance.

.claude/settings.local.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(make test-spm:*)",
5+
"Bash(make test-ios:*)"
6+
],
7+
"deny": [],
8+
"ask": []
9+
}
10+
}

.github/workflows/test.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ on:
99
- master
1010

1111
jobs:
12-
build:
13-
runs-on: macOS-13
12+
# Device list: https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
13+
build-xcode-16:
14+
runs-on: macos-latest
1415
env:
15-
DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer
16+
DEVELOPER_DIR: /Applications/Xcode_16.2.app/Contents/Developer
1617

1718
steps:
1819
- uses: actions/checkout@v1
20+
- uses: maxim-lobanov/setup-xcode@v1
21+
with:
22+
xcode-version: "16.2.0"
1923
- name: Run iOS tests
2024
run: make test-ios
2125
- name: Run tvOS tests

Demo/Demo-iOS/AppDelegate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import UIKit
22

3-
@UIApplicationMain
3+
@main
44
class AppDelegate: UIResponder, UIApplicationDelegate {
55
var window: UIWindow?
66

Demo/Demo.xcodeproj/project.pbxproj

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 46;
6+
objectVersion = 54;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -217,8 +217,9 @@
217217
9D98822F19BC69CA00B790C6 /* Project object */ = {
218218
isa = PBXProject;
219219
attributes = {
220+
BuildIndependentTargetsInParallel = YES;
220221
LastSwiftUpdateCheck = 1120;
221-
LastUpgradeCheck = 1200;
222+
LastUpgradeCheck = 1640;
222223
ORGANIZATIONNAME = "Kaishin & Co";
223224
TargetAttributes = {
224225
0007E16523809E9C000FED9F = {
@@ -338,16 +339,19 @@
338339
GCC_C_LANGUAGE_STANDARD = gnu11;
339340
INFOPLIST_FILE = "$(SRCROOT)/Demo-tvOS/Info.plist";
340341
INFOPLIST_KEY_CFBundleDisplayName = Gifu;
341-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
342+
LD_RUNPATH_SEARCH_PATHS = (
343+
"$(inherited)",
344+
"@executable_path/Frameworks",
345+
);
342346
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
343347
MTL_FAST_MATH = YES;
344348
PRODUCT_BUNDLE_IDENTIFIER = "com.redalemeden.Gifu-tvOSDemo";
345349
PRODUCT_NAME = "$(TARGET_NAME)";
346350
SDKROOT = appletvos;
347351
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
348-
SWIFT_VERSION = 5.0;
352+
SWIFT_VERSION = 6.0;
349353
TARGETED_DEVICE_FAMILY = 3;
350-
TVOS_DEPLOYMENT_TARGET = 13.2;
354+
TVOS_DEPLOYMENT_TARGET = 18.0;
351355
};
352356
name = Debug;
353357
};
@@ -368,22 +372,27 @@
368372
GCC_C_LANGUAGE_STANDARD = gnu11;
369373
INFOPLIST_FILE = "$(SRCROOT)/Demo-tvOS/Info.plist";
370374
INFOPLIST_KEY_CFBundleDisplayName = Gifu;
371-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
375+
LD_RUNPATH_SEARCH_PATHS = (
376+
"$(inherited)",
377+
"@executable_path/Frameworks",
378+
);
372379
MTL_FAST_MATH = YES;
373380
PRODUCT_BUNDLE_IDENTIFIER = "com.redalemeden.Gifu-tvOSDemo";
374381
PRODUCT_NAME = "$(TARGET_NAME)";
375382
SDKROOT = appletvos;
376-
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
377-
SWIFT_VERSION = 5.0;
383+
SWIFT_COMPILATION_MODE = wholemodule;
384+
SWIFT_OPTIMIZATION_LEVEL = "-O";
385+
SWIFT_VERSION = 6.0;
378386
TARGETED_DEVICE_FAMILY = 3;
379-
TVOS_DEPLOYMENT_TARGET = 13.2;
387+
TVOS_DEPLOYMENT_TARGET = 18.0;
380388
};
381389
name = Release;
382390
};
383391
9D98825119BC69CA00B790C6 /* Debug */ = {
384392
isa = XCBuildConfiguration;
385393
buildSettings = {
386394
ALWAYS_SEARCH_USER_PATHS = NO;
395+
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
387396
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
388397
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
389398
CLANG_CXX_LIBRARY = "libc++";
@@ -413,6 +422,7 @@
413422
COPY_PHASE_STRIP = NO;
414423
ENABLE_STRICT_OBJC_MSGSEND = YES;
415424
ENABLE_TESTABILITY = YES;
425+
ENABLE_USER_SCRIPT_SANDBOXING = YES;
416426
GCC_C_LANGUAGE_STANDARD = gnu99;
417427
GCC_DYNAMIC_NO_PIC = NO;
418428
GCC_NO_COMMON_BLOCKS = YES;
@@ -428,7 +438,7 @@
428438
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
429439
GCC_WARN_UNUSED_FUNCTION = YES;
430440
GCC_WARN_UNUSED_VARIABLE = YES;
431-
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
441+
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
432442
MTL_ENABLE_DEBUG_INFO = YES;
433443
ONLY_ACTIVE_ARCH = YES;
434444
SDKROOT = iphoneos;
@@ -440,6 +450,7 @@
440450
isa = XCBuildConfiguration;
441451
buildSettings = {
442452
ALWAYS_SEARCH_USER_PATHS = NO;
453+
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
443454
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
444455
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
445456
CLANG_CXX_LIBRARY = "libc++";
@@ -469,6 +480,7 @@
469480
COPY_PHASE_STRIP = YES;
470481
ENABLE_NS_ASSERTIONS = NO;
471482
ENABLE_STRICT_OBJC_MSGSEND = YES;
483+
ENABLE_USER_SCRIPT_SANDBOXING = YES;
472484
GCC_C_LANGUAGE_STANDARD = gnu99;
473485
GCC_NO_COMMON_BLOCKS = YES;
474486
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -477,7 +489,7 @@
477489
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
478490
GCC_WARN_UNUSED_FUNCTION = YES;
479491
GCC_WARN_UNUSED_VARIABLE = YES;
480-
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
492+
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
481493
MTL_ENABLE_DEBUG_INFO = NO;
482494
SDKROOT = iphoneos;
483495
VALIDATE_PRODUCT = YES;
@@ -493,13 +505,16 @@
493505
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
494506
INFOPLIST_FILE = "$(SRCROOT)/Demo-iOS/Info.plist";
495507
INFOPLIST_KEY_CFBundleDisplayName = Gifu;
496-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
497-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
508+
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
509+
LD_RUNPATH_SEARCH_PATHS = (
510+
"$(inherited)",
511+
"@executable_path/Frameworks",
512+
);
498513
OTHER_SWIFT_FLAGS = "";
499514
PRODUCT_BUNDLE_IDENTIFIER = net.4rays.Gifu.Demo;
500515
PRODUCT_NAME = "Gifu-iOSDemo";
501516
PROVISIONING_PROFILE_SPECIFIER = "";
502-
SWIFT_VERSION = 5.0;
517+
SWIFT_VERSION = 6.0;
503518
TARGETED_DEVICE_FAMILY = "1,2";
504519
};
505520
name = Debug;
@@ -513,14 +528,18 @@
513528
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
514529
INFOPLIST_FILE = "$(SRCROOT)/Demo-iOS/Info.plist";
515530
INFOPLIST_KEY_CFBundleDisplayName = Gifu;
516-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
517-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
531+
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
532+
LD_RUNPATH_SEARCH_PATHS = (
533+
"$(inherited)",
534+
"@executable_path/Frameworks",
535+
);
518536
OTHER_SWIFT_FLAGS = "";
519537
PRODUCT_BUNDLE_IDENTIFIER = net.4rays.Gifu.Demo;
520538
PRODUCT_NAME = "Gifu-iOSDemo";
521539
PROVISIONING_PROFILE_SPECIFIER = "";
522-
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
523-
SWIFT_VERSION = 5.0;
540+
SWIFT_COMPILATION_MODE = wholemodule;
541+
SWIFT_OPTIMIZATION_LEVEL = "-O";
542+
SWIFT_VERSION = 6.0;
524543
TARGETED_DEVICE_FAMILY = "1,2";
525544
};
526545
name = Release;

Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo-iOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1510"
3+
LastUpgradeVersion = "1640"
44
version = "1.7">
55
<BuildAction
66
parallelizeBuildables = "YES"

Gifu.podspec

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ Pod::Spec.new do |s|
77
s.license = { :type => "MIT", :file => "LICENSE" }
88
s.author = { "Reda Lemeden" => "[email protected]" }
99
s.source = { :git => "https://github.com/kaishin/Gifu.git", :tag => "v#{s.version}", :submodules => true }
10-
s.platform = :ios, "9.0"
11-
s.platform = :tvos, "9.0"
12-
s.swift_versions = ["5.0", "5.1", "5.2", "5.3", "5.4"]
10+
s.platform = :ios, "14.0"
11+
s.platform = :tvos, "14.0"
12+
s.swift_versions = ["5.0", "5.1", "5.2", "5.3", "5.4", "6.0", "6.1", "6.2"]
1313
s.ios.source_files = "Sources/**/*.{h,swift}"
1414
s.tvos.source_files = "Sources/**/*.{h,swift}"
15-
s.ios.deployment_target = "9.0"
16-
s.tvos.deployment_target = "9.0"
15+
s.ios.deployment_target = "14.0"
16+
s.tvos.deployment_target = "14.0"
1717
end

0 commit comments

Comments
 (0)