惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
Vercel News
Vercel News
C
Check Point Blog
G
Google Developers Blog
博客园 - 司徒正美
量子位
Engineering at Meta
Engineering at Meta
S
SegmentFault 最新的问题
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
A
About on SuperTechFans
美团技术团队
D
DataBreaches.Net
Stack Overflow Blog
Stack Overflow Blog
Jina AI
Jina AI
Y
Y Combinator Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Apple Machine Learning Research
Apple Machine Learning Research
J
Java Code Geeks
MongoDB | Blog
MongoDB | Blog
人人都是产品经理
人人都是产品经理
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The Cloudflare Blog
U
Unit 42

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
WWDC 2026 - Migrate to Swift Testing: What Actually Means...
ArshTechPro · 2026-06-16 · via DEV Community

Swift Testing shipped with Xcode 16 back in 2024.

Swift Testing was built from the ground up for Swift. That means Swift concurrency is a first-class citizen, test cases run in parallel by default, and the API surface is dramatically smaller than XCTest's forty-plus assertion functions. One macro, #expect, replaces most of them.

If you are still on XCTest, you have probably felt the friction: class inheritance for every test suite, function names that must start with test, assertion messages that tell you what the values were but not where the expression came from. Swift Testing fixes all of this.

That said, you do not need to migrate everything at once, and WWDC 2026 is emphatic about this.


The Migration Strategy: Small Chunks, No Big Bang

The session opens with something refreshing: permission to be slow about this.

The recommended approach is to leave your existing XCTests where they are and start using Swift Testing only for new tests. Both frameworks can coexist in the same target and even the same file. You do not need a separate test target, and you do not need a migration sprint.

The one rule: Swift Testing tests cannot live inside XCTestCase subclasses. Everything else is fair game.


Raw Identifiers for Readable Test Names

One small quality-of-life improvement worth knowing about from the start: Swift supports raw identifiers using backticks, and Swift Testing takes full advantage of this.

import Testing

@testable import DemoApp

@Test func `Default climate: tropical`() async throws {
    let fruit = Fruit(name: "Coconut")
    #expect(fruit.climate == .tropical)
}

No more testDefaultClimateTropical or dealing with camelCase names in test output. The test name is the test name.


Interoperability: The Key to Reusing Your Helper Code

This is the main new story in WWDC 2026 and the feature that makes incremental migration actually work.

The problem: you have test helper functions that wrap XCTFail. You want to call them from new Swift Testing tests. Previously, this was messy. Now, it works by design.

Interoperability is a feature that lets you safely call API from one test framework inside a test written in the other. So a Swift Testing test can call a helper that internally uses XCTFail, and XCTest tests can use Swift Testing's Issue.record and expectation macros.

Three Interoperability Modes

The session introduces three modes, and understanding them is important for knowing what level of strictness you want:

Limited mode -- Cross-framework issues from XCTest become warnings, not errors. Tests still pass. This is the default for test plans created before Xcode 27.

Complete mode -- Those same warnings become errors. The test will fail. This is the default for new projects in Xcode 27.

Strict mode -- Cross-framework issues from XCTest cause a fatal error and stop the test immediately at the point of the bad call. This is useful when you want to systematically find every place you need to replace XCTest API.

You can change modes in your Test Plan settings under "Test Execution," or for Swift Package projects using an environment variable:

SWIFT_TESTING_XCTEST_INTEROP_MODE=strict swift test

For SPM projects, complete mode requires bumping to swift-tools-version: 6.4 or newer.

Migrating a Helper Function

Here is a typical helper before migration:

func assertUnique(_ fruits: [Fruit], file: StaticString = #filePath, line: UInt = #line) {
    var uniqueNames = Set<String>()
    for name in fruits.map(\.name) {
        if !uniqueNames.insert(name).inserted {
            XCTFail("Duplicate name: \(name)", file: file, line: line)
        }
    }
}

After migrating to Swift Testing:

import Testing

func assertUnique(_ fruits: [Fruit], sourceLocation: SourceLocation = #sourceLocation) {
    var uniqueNames = Set<String>()
    for name in fruits.map(\.name) {
        if !uniqueNames.insert(name).inserted {
            Issue.record("Duplicate name: \(name)", sourceLocation: sourceLocation)
        }
    }
}

XCTFail becomes Issue.record. The file and line parameters become a single SourceLocation parameter. The helper can now be called cleanly from both Swift Testing tests and existing XCTests.


Common Migration Patterns

The session walks through two patterns that come up in almost every migration.

Skipping Tests

XCTest uses XCTSkipIf. In Swift Testing, the direct replacement is Test.cancel, but the preferred approach is a trait:

let isFall = false

// XCTest
func testSwallowFallMigration() async throws {
    try XCTSkipIf(!isFall, "Wrong season for migration")
}

// Swift Testing via Test.cancel (works but not ideal)
func testSwallowFallMigration() async throws {
    if !isFall {
        try Test.cancel("Wrong season for migration")
    }
}

// Preferred: use a trait
@Test(.enabled(if: isFall, "Wrong season for migration"))
func `Swallow fall migration`() async throws {
    // ...
}

Moving the condition into a trait keeps the test body clean and makes the enablement logic visible at a glance.

Halting After Failures

In XCTest, you set continueAfterFailure = false to stop on the first failure. In Swift Testing, you use #require instead of #expect for the assertions where failure should halt the test:

func testExample() async throws {
    #expect(Fruit.banana.climate == .temperate)

    // If this fails, the test stops here
    try #require(Fruit.banana == Fruit.plantain)

    // This line is only reached if #require passed
}

The benefit over XCTest's approach: you get fine-grained control over which expectations are fatal and which are not, rather than a single global flag.


What You Unlock After Migrating

The second half of the session is about capabilities that simply do not exist in XCTest.

Parameterized Tests

This is one of the most impactful changes for test suites that have grown unwieldy with repetitive test methods.

Before, you might write a nested loop inside a single test:

@Test func `Birds flap wings successfully`() async throws {
    for bird in Aviary.birds {
        for count in (40...100) {
            try await bird.flapWings(count: count)
        }
    }
}

The problem: when it fails, you do not know which bird or which count triggered the failure. The whole loop is one test case.

After converting to a parameterized test:

@Test(arguments: Aviary.birds, 40...100)
func `Birds flap wings successfully`(bird: Bird, count: Int) async throws {
    try await bird.flapWings(count: count)
}

Swift Testing generates a separate test case for every combination of bird and count. All cases run in parallel. When something fails, the Test navigator shows you exactly which inputs caused the failure. In the session's demo, the refactored test also finished significantly faster because of parallel execution.

Exit Tests

Exit tests let you write coverage for code that is expected to crash -- preconditionFailure, fatalError, and similar calls that have historically been impossible to test without crashing your entire test process.

Given this code in a Bird initializer:

if name.isEmpty {
    preconditionFailure("Bird name cannot be empty")
}

You can now write:

@Test func `Bird with empty name crashes`() async throws {
    await #expect(processExitsWith: .failure) {
        _ = Bird(name: "")
    }
}

Swift Testing runs the body of the exit test in a child process. If that process exits with the expected condition, the test passes. The crash is isolated, so it cannot affect any other test. Exit tests are supported on macOS, Linux, FreeBSD, and Windows.

This finally gives you a way to get code coverage on defensive guards that were previously invisible to your test suite.


What Stays in XCTest

The session is clear about what not to migrate:

  • UI automation tests using XCUIApplication
  • Performance tests using XCTMetric
  • Tests that catch Objective-C exceptions (only Objective-C code can handle these safely)

For everything else, Swift Testing is the better home.


Xcode's Migration Assistance

One practical note from the session: Xcode 27's Coding Assistant is aware of the migration documentation and can help formulate a strategy, review your work, and automate parts of the migration. If you are staring at a large test suite and not sure where to start, that is worth exploring.


The Summary

The path forward is clear and low-risk:

  1. Keep your existing XCTests where they are. Do not touch them until you are ready.
  2. Write all new tests using Swift Testing.
  3. Enable interoperability and start with limited mode. Gradually move to complete or strict as you migrate individual helpers.
  4. When you update a helper, replace XCTFail with Issue.record and update the source location parameter.
  5. Look for nested loops in your tests -- those are candidates for parameterized tests.
  6. Add exit tests anywhere you have preconditionFailure or fatalError with no coverage.

The migration is not a one-time event. It is a gradual shift that Xcode 27 is explicitly designed to support.