XCTest ties every test to a class, a test-prefixed method name, and an assertion that halts the method on first failure — fine for years, but verbose for the common case of "run this logic against a handful of inputs."
#The old way
import XCTest
final class PriceFormatterTests: XCTestCase {
func testFormatsWholeDollars() {
let formatter = PriceFormatter()
XCTAssertEqual(formatter.format(cents: 500), "$5.00")
}
func testFormatsFractionalCents() {
let formatter = PriceFormatter()
XCTAssertEqual(formatter.format(cents: 599), "$5.99")
}
}
#The new way
import Testing
struct PriceFormatterTests {
@Test("Formats cents as dollars", arguments: [
(500, "$5.00"),
(599, "$5.99"),
(0, "$0.00")
])
func formatsCents(cents: Int, expected: String) {
let formatter = PriceFormatter()
#expect(formatter.format(cents: cents) == expected)
}
@Test
func throwsOnNegativeCents() {
let formatter = PriceFormatter()
#expect(throws: PriceError.self) {
try formatter.validate(cents: -100)
}
}
}
#Why it matters
arguments:gives real parameterised tests — one test body runs once per input, each reported as its own pass/fail, instead of one method looping over cases#expectkeeps evaluating past a failed check within the same test instead of aborting likeXCTAssert, so one run surfaces every failure, not just the first- Plain structs replace
XCTestCasesubclassing — notest-prefixed method names, no inherited setup/teardown boilerplate #expect(throws:)checks an error type or a specific error value inline, without a do/catch scaffold
#Gotcha
Swift Testing runs test methods within a suite concurrently by default, unlike XCTest's serial-by-default execution — tests that share mutable state (a singleton, a shared file, a static var) can interfere with each other in ways that only show up intermittently. Mark a suite @Suite(.serialized) if its tests can't run in parallel. Swift Testing and XCTest coexist in the same target, but UI testing (XCUIApplication) and performance tests (XCTMetric) still require XCTest — Swift Testing doesn't replace those. Check exact API details against current Xcode docs, since the framework is still evolving quickly.