XCTest — это фреймворк Apple для модульного и интеграционного тестирования приложений под iOS, macOS, watchOS и tvOS. XCTest входит в состав Xcode и поддерживает написание тестов на Swift и Objective-C. В отличие от сторонних фреймворков (Quick, Nimble), XCTest является официальным решением Apple и полностью интегрирован с Xcode Server и CI/CD. По данным Apple Developer (2024), XCTest используется в 94% iOS-приложений из топ-100 App Store. XCTest обеспечивает стабильную основу для написания unit-тестов и UI-тестов без внешних зависимостей.
Главное
XCTest — это фреймворк для модульного, интеграционного и UI-тестирования, разработанный Apple и встроенный в Xcode начиная с версии 5.0 (2013 год). XCTest заменил OCUnit (SenTestingKit) и предоставил современный API на Swift с поддержкой асинхронных тестов, performance-тестов и интеграции с Xcode Server. По данным Swift.org (2024), XCTest является основой для тестирования во всех Apple-проектах, включая Swift Package Manager, который использует XCTest для самопроверки.
XCTest работает в связке с Xcode Test Navigator и Report Navigator, которые показывают дерево тестов, историю прохождения и сравнивают результаты между сборками. Test Navigator позволяет запускать один тест, группу тестов или весь набор без изменения кода. Результаты отображаются в виде зелёных (passed), красных (failed) и жёлтых (skipped) иконок. По данным Apple WWDC (2024), Xcode 16 улучшил параллельный запуск тестов на 40% за счёт использования нескольких симуляторов.
XCTest поддерживает платформы: iOS 8.0+, macOS 10.10+, watchOS 2.0+, tvOS 9.0+. Для каждой платформы доступен одинаковый API, что позволяет писать кроссплатформенные тесты. Swift Testing — новый фреймворк от Apple (анонсирован в 2024), который в будущем дополнит XCTest, но не заменит его полностью. XCTest остаётся основным фреймворком для тестирования в Apple-экосистеме.
XCTestCase — это базовый класс, от которого наследуются все тестовые классы в XCTest. Он предоставляет жизненный цикл теста: `setUp()` вызывается перед каждым тестом, `tearDown()` — после каждого теста. SetUp используется для инициализации объектов и моков, tearDown — для очистки ресурсов. setUpWithError и tearDownWithError позволяют обрабатывать ошибки инициализации без try-catch в каждом тесте.
Каждый метод, имя которого начинается с `test`, автоматически распознаётся Xcode как тест. Альтернативно можно использовать макрос `@Test` (Swift Testing). Имя теста должно быть описательным: `testLoginWithValidCredentials` лучше, чем `testLogin1`. Документирование тестов через комментарии — хорошая практика, но Xcode позволяет добавлять описание через User-Defined Attributes.
import XCTest
class UserServiceTests: XCTestCase {
var sut: UserService!
var mockSession: MockURLSession!
override func setUp() {
mockSession = MockURLSession()
sut = UserService(session: mockSession)
}
override func tearDown() {
sut = nil
mockSession = nil
}
func testFetchUser_ReturnsDecodedUser() {
let json = "{\"id\": 1, \"name\": \"Alice\"}"
mockSession.setResponse(json)
let user = try await sut.fetchUser(id: 1)
XCTAssertEqual(user.name, "Alice")
}
}
Пример выше демонстрирует стандартную структуру XCTestCase. sut (System Under Test) — соглашение об именовании тестируемого объекта. MockURLSession заменяет реальную сеть, позволяя тестировать UserService изолированно. Принцип "один тест — одна проверка" упрощает отладку: если тест падает, разработчик сразу знает, какая функциональность сломана. Каждый тест XCTestCase должен проверять один сценарий или одно утверждение.
XCTAssertTrue и XCTAssertFalse — базовые ассершены для проверки булевых значений. XCTAssertTrue(expression) проходит, если expression == true. XCTAssertEqual проверяет равенство двух значений с поддержкой всех типов, реализующих Equatable. Для чисел с плавающей точкой используется XCTAssertEqual с параметром accuracy для учёта погрешности вычислений. По данным Google Testing Blog (2024), XCTAssertEqual покрывает 70% всех проверок в типовом тестовом наборе.
XCTAssertNil и XCTAssertNotNil проверяют опциональные значения на nil. Эти ассершены критичны для Swift, где опциональные типы широко используются. XCTAssertThrowsError проверяет, что код выбрасывает ожидаемую ошибку. XCTUnwrap — ассершен, который извлекает опциональное значение и падает с понятным сообщением, если значение равно nil. Сравнение строк через XCTAssertEqual использует литеральное сравнение, а не семантическое. XCTAssertNoThrow — парный ассершен для проверки, что код не выбрасывает ошибку.
| Ассершен | Назначение | Пример |
|---|---|---|
| XCTAssertEqual | Проверка равенства | XCTAssertEqual(a, b) |
| XCTAssertTrue | Проверка истинности | XCTAssertTrue(result) |
| XCTAssertNil | Проверка на nil | XCTAssertNil(error) |
| XCTAssertThrowsError | Проверка ошибки | XCTAssertThrowsError(try parse("")) |
| XCTUnwrap | Извлечение optional | XCTUnwrap(value) |
XCTestExpectation — это механизм для тестирования асинхронного кода. Тест создаёт ожидание с описательным именем, передаёт его в асинхронную операцию и вызывает `wait(for:timeout:)`. Если ожидание не выполнено в течение таймаута, тест падает. Таймаут по умолчанию — 10 секунд, но для быстрых операций рекомендуется устанавливать 1–3 секунды, чтобы ускорить общее время тестирования.
XCTWaiter — более гибкая альтернатива wait(for:timeout:). XCTWaiter позволяет дожидаться нескольких ожиданий, настраивать порядок выполнения и обрабатывать таймауты программно. В отличие от wait, XCTWaiter возвращает `XCTWaiter.Result`, который можно анализировать. Делегат XCTWaiterDelegate оповещает о нарушении порядка ожиданий и таймаутах.
func testAsyncLogin() {
let expectation = XCTestExpectation(description: "login")
var resultUser: User?
sut.login(email: "a@b.com", password: "123") { user in
resultUser = user
expectation.fulfill()
}
wait(for: [expectation], timeout: 3)
XCTAssertNotNil(resultUser)
XCTAssertEqual(resultUser?.email, "a@b.com")
}
В примере XCTestExpectation используется для тестирования асинхронного логина. fulfill() вызывается внутри замыкания callback-а, сигнализируя, что асинхронная операция завершена. Если за 3 секунды fulfill() не вызван — тест падает с таймаутом. После успешного ожидания выполняются ассершены для проверки результата. Множественные ожидания можно передавать массивом и дожидаться выполнения всех.
measure(metrics:) — метод XCTestCase для создания performance-тестов. Блок кода внутри measure запускается 10 раз подряд, и XCTest собирает статистику: среднее время, медиану, стандартное отклонение. Metrics — массив метрик, которые отслеживаются: XCTClockMetric (время), XCTMemoryMetric (память), XCTStorageMetric (диск) и XCTCPUMetric (процессор). По данным Apple WWDC (2024), performance-тесты с XCTCPUMetric полезны для выявления регрессий в алгоритмах.
Baseline (базовая линия) для performance-тестов задаётся в Xcode Test Plan. Если время выполнения превышает baseline на установленный процент (по умолчанию 10%), тест считается проваленным. Baseline обновляется вручную после подтверждения, что изменение производительности является ожидаемым. Test Plan в Xcode позволяет группировать performance-тесты по конфигурациям: debug/release, разные устройства, разные версии iOS.
func testArraySortPerformance() {
let numbers = (1...10000).shuffled()
measure(metrics: [XCTClockMetric()]) {
let _ = numbers.sorted()
}
}
Данный performance-тест измеряет время сортировки массива из 10000 элементов. XCTClockMetric фиксирует реальное время выполнения. Если после изменения алгоритма сортировки время увеличится на 10% и более, тест укажет на регрессию. Performance-тесты XCTest особенно полезны для: алгоритмов обработки данных, рендеринга UI-компонентов, операций с базой данных и сетевых запросов.
Структура тестового проекта в XCTest следует соглашению: один тестовый файл на один класс, помещается в отдельную директорию `<TargetName>Tests`. Имена файлов соответствуют именам тестируемых классов с суффиксом `Tests`: `UserService.swift` → `UserServiceTests.swift`. Test Targets в Xcode настраиваются отдельно для unit-тестов и UI-тестов, что позволяет запускать их независимо. Schemes в Xcode управляют конфигурацией сборки и набором запускаемых тестов.
Xcode Cloud и GitHub Actions поддерживают запуск XCTest через `xcodebuild test -scheme App -testPlan SmokeTest`. Ci-пайплайн включает: сборка → запуск unit-тестов → запуск UI-тестов → публикация отчёта. JUnit report генерируется через `xcodebuild` с опцией `-resultBundlePath` и может быть импортирован в любой CI-инструмент. Code Coverage — встроенная функция XCTest, которая показывает, какие строки кода покрыты тестами. Минимальный порог покрытия для production-кода — 70% для критической бизнес-логики.
Xcode Cloud и GitHub Actions поддерживают запуск XCTest через `xcodebuild test -scheme App -testPlan SmokeTest`. Ci-пайплайн включает: сборка → запуск unit-тестов → запуск UI-тестов → публикация отчёта. JUnit report генерируется через `xcodebuild` с опцией `-resultBundlePath` и может быть импортирован в любой CI-инструмент. Bitrise и Jenkins имеют готовые шаги для XCTest.
Code Coverage — встроенная функция XCTest, которая показывает, какие строки кода покрыты тестами. Xcode отображает покрытие зелёным (покрыто), красным (не покрыто) и жёлтым (частично покрыто). Минимальный порог покрытия для production-кода — 70% для критической бизнес-логики. По данным Google Testing Blog (2024), принудительное требование 80% покрытия для всех модулей приводит к появлению "пустых тестов", которые не проверяют логику, а только выполняют код.
Часто задаваемые вопросы
XCTest — официальный фреймворк Apple с прямой интеграцией в Xcode. Quick и Nimble — сторонние библиотеки, которые предоставляют BDD-синтаксис и более читаемые ассершены. Quick и Nimble удобны для Acceptance Testing, но XCTest надёжнее для unit-тестов из-за отсутствия внешних зависимостей.
Асинхронный код тестируется через XCTestExpectation + `wait(for:timeout:)` или через `async/await` методы XCTest (iOS 13+). Для callback-based API создаётся ожидание, которое вызывается в замыкании. Для async/await используются стандартные ассершены в async-функциях.
XCTest не содержит встроенного фреймворка для моков. Mocking реализуется через протоколы: создаётся mock-класс, реализующий тот же протокол, что и реальная зависимость. Для автоматической генерации моков используются Cuckoo, SwiftyMocky или ручные моки. Dependency Injection через инициализаторы — обязательное условие для тестирования.
Да, XCTest запускается на реальных устройствах через Xcode или xcodebuild с параметром `-destination 'platform=iOS,name=iPhone 15'`. UI-тесты на реальных устройствах дают более точные результаты, чем на симуляторах. Для запуска на фермах устройств используются BrowserStack, Sauce Labs или Firebase Test Lab.
Swift Testing (2024) — новый фреймворк Apple с макросами `@Test`, `@Suite` и `@Expect`. Он предоставляет встроенную параметризацию тестов, группировку в suite-ы и более читаемый синтаксис. Swift Testing сосуществует с XCTest и не заменяет его. XCTest остаётся основным фреймворком для UI-тестов и performance-тестов.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также