С выходом Swift Testing многие задумались о миграции с XCTest. Но так ли всё гладко, как обещает Apple? Давайте разберём реальные подводные камни и лайфхаки для перехода.
🤔 Чем Swift Testing отличается от XCTest?
1️⃣ Обнаружение тестов.
🔹 XCTest: использует XCTestCase + Objective-C runtime.
🔹 Swift Testing: макросы
@Test + Swift ориентированный подход.2️⃣ Выполнение тестов.
🔹 XCTest: выполняется последовательно (один за другим).
🔹 Swift Testing: выполняется параллельно (каждый тест в отдельной Task).
3️⃣ Синтаксис.
Разница в коде:
// XCTest
func testLogin() { XCTAssertEqual(result, expected) }
// Swift Testing
@Test func login() { #expect(result == expected) }
⚠️ Главные проблемы миграции.
1️⃣ Ассерты вне контекста Task.
Swift Testing требует, чтобы проверки (
#expect) выполнялись только внутри Task.// Старая схема (XCTest)
func testAsync() {
DispatchQueue.main.async {
XCTAssertEqual(result, 42)
}
}
// Неправильная миграция (упадёт в Swift Testing)
@Test func asyncTest() {
DispatchQueue.main.async {
#expect(result == 42) // Ошибка: вне контекста Task
}
}
// Правильная миграция
@Test func asyncTest() async { // 1. Объявляем async тест
let result = await withCheckedContinuation { continuation in
DispatchQueue.main.async {
continuation.resume(returning: 42) // 2. Возвращаем результат в контекст Task
}
}
#expect(result == 42) // 3. Проверяем УЖЕ внутри Task-контекста
}
Ключевые моменты:
🔸 Тест должен быть async — это автоматически создаёт контекст Task.
🔸 Все асинхронные операции, которые не поддерживают async/await оборачиваются в withCheckedContinuation.
🔸
#expect вызывается после получения результата, но внутри области теста.2️⃣ Проблемы с общим состоянием.
Параллельное выполнение тестов могут вызвать гонки данных.
♣️ Пример:
actor Counter {
var count = 0
func increment() {
count += 1
}
}
@Test func testA() async {
await Counter.shared.increment()
#expect(await Counter.shared.count == 1) // Может упасть, если testB запустится раньше!
}
@Test func testB() async {
await Counter.shared.increment()
}✅ Решение:
Использование
@Suite(.serialized) для последовательного выполнения:@Suite(.serialized)
struct CriticalTests {
@Test func testA() {
}
@Test func testB() {
}
}
3️⃣ Наследование тестов не работает.
В XCTest можно было наследовать XCTestCase и автоматически получать все тесты родителя. В Swift Testing так не работает — тесты нужно дублировать.
Что делать:
🔸 Для простых сценариев использовать параметризованные тесты.
🔸 Для сложных, пока оставьте XCTest и мигрируйте постепенно.
💡 Вывод:
На Swift Testing стоит переходить если у вас современный проект, который использует Swift Concurrency и вам необходимо параллельное выполнение тестов.
Мой совет: Начинайте с гибридного подхода — подключайте Swift Testing для новых модулей, а старые тесты оставляйте в XCTest.
➡️ Подписаться на канал
Мобильный трудоголик