Статьи
Введение в Swift Testing
На WWDC 2024 одним из самых интересных инструментов был Swift Testing, который делает тестирование Swift-кода более мощным, чем когда-либо. С его помощью разработчики могут уверенно создавать высококачественные продукты с минимальным количеством кода.
Swift Testing включает в себя такие инновационные функции, как встроенная поддержка параллелизма, параллельное выполнение тестов и внедрение макросов тестирования.
Предварительные условия
Прежде чем использовать Swift Testing, убедитесь, что у вас есть Xcode 16.0+ и Swift 6.0+.
Как добавить цель Swift Testing?
Чтобы начать работу с Swift Testing, необходимо выбрать Swift Testing with XCTest UI Tests при создании нового проекта.
Это позволяет писать тестовые случаи в соответствующих тестовых таргетах.
Строительные блоки
Очень важно убедиться в том, что ваши тесты читабельны, особенно по мере усложнения кодовой базы. В Swift Testing добавлены некоторые функции, помогающие в этом, поэтому вам будет проще писать выразительные тесты.
В общей сложности в нем присутствует 4 строительных блока. Ниже вы найдете подробную информацию о каждом из них.
1. Функции тестирования
Функция, аннотированная @Test, обозначает тестовую функцию. Где бы вы ни использовали эту аннотацию, Xcode покажет вам бриллиантовую кнопку.
- Это может быть глобальная функция или метод класса.
- Она может быть помечена как async или throws.
2. Ожидания (Expectations)
Макрос #expect
Макрос #expect проверяет, истинно ли ожидаемое условие или нет, с помощью операторов и выражений. Он может обрабатывать сложные проверки.
Если произошел сбой, будет выведена подробная информация о нем и это позволит вам понять, что пошло не так.
Макрос #expect(.throws: (any Error).self) { }
Вместо того чтобы писать оператор catch вручную, используйте макросы expect throws, которые делают обработку исключений проще и эффективнее.
Если этот блок не выбросит ни одной ошибки, он завершится неудачей. В противном случае он будет успешным.
/// This snippet provides functions to demonstrate testing scenarios with the #expect(throws:) macros.
enum APIError: Error {
case resourceNotFound
}
func throwingAnError() throws {
print("A function that throw an error")
throw APIError.resourceNotFound
}
func notThrowingAnError() throws {
print("A function that does not throw an error")
}
Вы можете взглянуть на тестовый пример ниже:
В приведенном выше примере функция, выбрасывающая ошибку, успешно прошла тест, а функция, не выбрасывающая ошибок, не прошла тест.
Макрос #require
Его можно использовать, если вы хотите завершить тест раньше времени, если ожидание не сработало. Он останавливает тест, если заданное условие ложно, или разворачивает необязательные значения и прекращает тестирование, если выражение оказывается равным nil.
В нем есть ключевой оператор try, который выбрасывает ошибку, если выражение ложно или содержит значение nil, останавливая дальнейшее выполнение.
В данном случае ожидание полагается на требуемое значение, которое оказалось равным нулю, что привело к остановке выполнения теста.
3. Трейты (Traits)
Трейты помогают добавить описательную информацию о тестах и позволяют настраивать, когда или будет ли вообще выполняться тест.
Выберите .tags() вместо @Test("test name"), чтобы включить или исключить тесты.
Используйте трейты с умом, не в каждой ситуации нужны теги. Например, если вы имеете дело с условиями рантайма, лучше использовать .enable() вместо .tag().
4. Тестовые наборы (Test Suites)
Наборы тестов группируют тестовые функции и другие наборы.
Логика установки и завершения работы реализована с помощью init() и deinit(), соответственно. init() вызывается перед запуском теста, а deinit() — сразу после его завершения.
Каждая тестовая функция запускается независимо на своем экземпляре, поэтому они никогда не обмениваются данными по ошибке.
Пример тестового набора:
@Suite("Basic calculator operations")
struct TestCalculation {
var calc = Calculator()
@Test func testAddition() async throws {
#expect(calc.addition(valA: 10, valB: 20) == 30)
}
@Test func testSubtraction() async throws {
#expect(calc.subtraction(valA: 20, valB: 10) == 10)
}
@Suite("Advance calculator operations")
struct AdvanceTestCalculation {
var calc = Calculator()
@Test func testPower() async throws {
#expect(calc.power(radix: 2, power: 3) == 8)
}
}
}
Calculator.swift используется в TestCalculation.swift:
struct Calculator {
func addition(valA: Int, valB: Int) -> Int {
return valA + valB
}
func subtraction(valA: Int, valB: Int) -> Int {
return valA - valB
}
func power(radix: Int, power: Int) -> Int {
var tempBase = radix
var tempPower = power
var result: Int = 1
while (tempPower != 0) {
if (tempPower % 2 == 1) {
result *= tempBase
}
tempPower = tempPower >> 1
tempBase *= tempBase
}
return result
}
}
Здесь вы можете увидеть оба набора в навигаторе тестов с указанными именами тестов. Поднабор отображается иерархически под родительским набором.
Параметризованное тестирование
Swift Testing предлагает параметризованные тесты, которые позволяют запускать одну тестовую функцию с несколькими различными аргументами. Это делает тестирование более эффективным и управляемым.
Параметризованное тестирование помогает обеспечить тщательное покрытие тестами без избыточного кода или создания чрезмерного количества отдельных тестовых случаев.
Давайте получим практический опыт работы с параметризованными тестами.
Вышеописанная ошибка указывает на неправильный синтаксис параметризованных тестов в Swift-тестировании. Мы должны использовать аргументы в атрибуте @Test.
import Testing
struct ParameterizedTests {
@Test(arguments: [
"abc123@gmail.com",
"abc123gmail.com",
"abc123@.com"
]) func testValidEmail(email: String) {
#expect(email.contains("@") && email.contains("."), "Invalid email format")
}
}
В приведенном выше примере указано три аргумента. Если два из них верны, а один неверен, то весь тестовый пример будет помечен как неудачный. Однако в навигаторе тестов можно увидеть, какие аргументы привели к неудаче.
Вместе с аргументами можно указать дополнительные трейты или имена отображения.
@Test("Email validation", arguments: [
"abc123@gmail.com",
"abc123gmail.com",
"abc123@.com"
]) func testValidEmail(email: String) {
#expect(email.contains("@") && email.contains("."), "Invalid email format")
}
Положительные аспекты:
- Просмотр подробной информации о каждом аргументе в результатах.
- Повторное выполнение определенных аргументов при необходимости отладки функции.
- Тесты могут выполняться более эффективно за счет параллельного выполнения каждого аргумента.
Сравнение XCTest и Swift Test
Если вы знакомы с XCTest, вам может быть интересно, чем XCTest отличается от Swift Test.
Сравнение строительных блоков в тестах Swift и XCTest.
1. Тестовая функция

Ожидания в обоих фреймворках сильно отличаются.
XCTest использует специальные проверки, начиная с XCTAssert, чтобы убедиться, что все работает так, как ожидается.
Однако в Swift Test есть два основных макроса — #expect и #require.

Советы по миграции с XCTest
- XCTest и Swift Test могут быть написаны в одной цели. Нет необходимости создавать новую цель.
- Если несколько методов имеют схожую структуру, их можно объединить в один тест и сделать эту функцию параметризованной.
- Объедините отдельные классы XCTest в одну глобальную функцию
@Test. - Префикс
testв именах функций больше в Swift Test не нужен. Вы можете удалить его из функций. - Избегайте использования XCTAssertion в Swift-тестировании и макросов тестирования типа
#expectи#requireв XCTest.
Продолжайте использовать XCTest
Отказаться от XCTest сложно, если:
- Тесты используют API автоматизации работы с UI, например XCUIApplication, или API метрик производительности, например XCTMetric, которые не поддерживаются в Swift тестировании.
- Тесты могут быть написаны только на Objective-C.
Заключение
Swift Testing предлагает выразительный API с макросами, позволяющий объявлять поведение тестов с минимальным количеством кода. API #expect использует выражения и операторы Swift для проверки предоставленных выражений. Параметризованные тесты помогают сократить количество избыточного кода, позволяя использовать код с похожей структурой. Кроме того, Swift Testing поддерживает параллельное тестирование, что повышает общую эффективность и производительность тестирования. Однако если вы хотите включить сериализованное тестирование, вам необходимо использовать трейт, чтобы сделать его сериализованным.
-
Аналитика магазинов2 недели назад
Мобильный рынок Ближнего Востока: исследование Bidease и Sensor Tower выявляет драйверы роста
-
Интегрированные среды разработки3 недели назад
Chad: The Brainrot IDE — дикая среда разработки с играми и развлечениями
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.45
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.46


2. Ожидания (Expectations)








