Обзор тестирования в Swift
Тестирование кода является важной и неотъемлемой частью процесса разработки программного обеспечения. Оно позволяет разработчикам проверять функциональность своих программ, выявлять ошибки и обеспечивать стабильную работу кода при внесении изменений. В экосистеме Swift разработчики могут воспользоваться несколькими инструментами для написания тестов, такими как XCTest, а также новыми библиотеками, такими как Testing.
XCTest является основным инструментом для тестирования в Swift и широко используется разработчиками. Однако, новая библиотека Testing предлагает дополнительные возможности и синтаксический сахар, который делает процесс тестирования еще более удобным и мощным. В данной статье мы рассмотрим основные аспекты тестирования на Swift, включая использование библиотеки Testing.
Зачем нужны тесты и какие виды тестов существуют
Тесты необходимы по нескольким причинам:
Обеспечение корректности работы кода: Тесты помогают убедиться, что ваш код работает так, как ожидается. Это особенно важно в больших проектах, где один неправильный шаг может привести к множеству проблем.
Защита от регрессий: Когда вы вносите изменения в код, тесты помогают убедиться, что новые изменения не сломали существующую функциональность.
Документация поведения системы: Тесты могут служить живой документацией для вашего кода, показывая, как именно он должен работать.
Повышение уверенности разработчиков: Наличие тестов позволяет разработчикам быть уверенными в том, что их изменения не приводят к неожиданным последствиям.
Существует несколько основных видов тестов:
Юнит-тесты (Unit Tests): Тестируют отдельные компоненты или функции кода. Эти тесты изолированы от других частей системы и проверяют работу конкретных функций или методов.
Интеграционные тесты (Integration Tests): Проверяют взаимодействие между различными частями системы. Эти тесты помогают убедиться, что модули системы работают вместе правильно.
Системные тесты (System Tests, E2E): Проверяют систему в целом. Эти тесты обычно включают проверку всех аспектов системы, включая пользовательский интерфейс.
Приемочные тесты (Acceptance Tests, QA): Валидируют систему с точки зрения требований пользователя. Эти тесты помогают убедиться, что система удовлетворяет требованиям конечного пользователя.
Основные понятия: test target, test function, test suite
Чтобы успешно писать тесты в Swift, необходимо понять несколько основных понятий:
Test Target: В Xcode для тестирования создается специальная цель (target), которая содержит все ваши тесты. Это позволяет отделить тесты от основной кода базы и управлять ими отдельно. Для добавления тестов в проект, необходимо создать новый test target в вашем Xcode проекте.
Test Function: Это отдельная функция, которая содержит проверочный код. В XCTest такие функции помечаются специальным префиксом test, например, func testExample(). В библиотеке Testing для этого используется атрибут @Test. Эти функции выполняются тестовым фреймворком и сообщают о результатах выполнения.
import Testing
@Test
func testExample() {
let result = add(2, 3)
#expect(result == 5)
}
Test Suite: Группа тестов, объединенных для совместного выполнения. В XCTest вы можете создавать тестовые наборы, наследуясь от XCTestCase. В библиотеке Testing тестовые наборы можно организовывать в типы с аннотацией @Suite. При работе с большим количеством тестовых функций организация их в тестовые наборы может значительно повысить ясность и управляемость. @Suite позволяет создавать тестовые наборы, которые представляют собой типы, содержащие несколько тестовых функций. Такой подход не только улучшает структуру, но и позволяет расширить возможности настройки и контроля над выполнением тестов.
import Testing
@Suite
struct ArithmeticTests {
@Test
func testAddition() {
let result = add(2, 3)
#expect(result == 5)
}
@Test
func testSubtraction() {
let result = subtract(5, 3)
#expect(result == 2)
}
}
Работа с библиотекой тестирования
Подключение библиотеки Testing
Для начала работы с библиотекой Testing необходимо импортировать её в файл с тестами. (Используйте XCode 16+)
import Testing
Важно: Импортируйте библиотеку тестирования только в тестовые цели. Импортирование её в целевые приложения, библиотеки или бинарные файлы не поддерживается и не рекомендуется.
Определение тестовой функции
Чтобы определить тестовую функцию, необходимо создать функцию Swift, которая не принимает аргументы и перед её именем поставить атрибут @Test:
@Test func foodTruckExists() {
// Логика теста
}
Также можно задать пользовательское имя теста:
@Test("Food truck exists") func foodTruckExists() { ... }
Асинхронные и бросающие тесты
Тестовые функции могут быть асинхронными (async
) и бросающими исключения (throws
), что позволяет выполнять асинхронные операции и обрабатывать ошибки:
@Test @MainActor func foodTruckExists() async throws { ... }
Тестирование с использованием параметров
Тестовые функции могут принимать аргументы для проверки функции на разных данных без дублирования кода:
@Suite
struct MultiplicationTests {
@Test(arguments: [(2, 3, 6), (4, 5, 20), (7, 8, 56)])
func testMultiplication(_ a: Int, _ b: Int, _ expected: Int) {
let result = multiply(a, b)
#expect(result == expected)
}
}
@Test(arguments: [
FoodTruck(wheels: 4, name: "Ford"),
FoodTruck(wheels: 3, name: "Ford"),
FoodTruck(wheels: 5, name: "Ford")
])
func testAdditionParameterized(foodtruck: FoodTruck) {
assert(foodtruck.wheels >= 4, "Ожидалось, что у фултрака минимум 4 колеса")
}
Ограничение доступности тестов
Если тест должен выполняться только на определенных версиях операционной системы или языка Swift, используйте атрибут @available
:
@available(macOS 11.0, *)
@available(swift, introduced: 8.0, message: "Requires Swift 8.0 features to run")
@Test func foodTruckExists() { ... }
Тестирование в основном потоке
Для тестирования кода, который должен выполняться в основном потоке (например, обновление интерфейса), используйте атрибут @MainActor
:
@Test @MainActor func mainThreadTestExample() async throws {
// Логика теста в основном потоке
}
Примеры и пояснения
Пример 1: Тест асинхронной функции
struct NetworkManager {
func fetchData() async throws -> Data {
// Имитация сетевого запроса
return Data()
}
}
@Test
func testFetchData() async throws {
let networkManager = NetworkManager()
let data = try await networkManager.fetchData()
#expect(data.isEmpty == false)
}
Этот пример демонстрирует тестирование асинхронной функции fetchData
в классе NetworkManager
. Функция testFetchData
помечена атрибутами async
и throws
, что позволяет ей выполнять асинхронные операции и обрабатывать ошибки с помощью try
. Утверждение #expect
проверяет, что возвращенные данные не пусты.
Пример 2: Тест в основном потоке
class ViewModel {
func updateState(state: ViewModel.State) {
self.state = state
// Обновление пользовательского интерфейса
}
}
@Test
@MainActor
func testUpdateUI() async throws {
let viewModel = ViewModel()
await viewModel.updateState(state: .loading)
#expect(viewModel.state == .loading)
// Дополнительные проверки, связанные с обновлением интерфейса
}
Этот пример показывает тестирование функции updateState
в классе ViewModel
, которая изменяет состояние модели и обновляет пользовательский интерфейс. Атрибут @MainActor
гарантирует, что тест testUpdateUI
будет выполняться в основном потоке, что важно для правильного функционирования пользовательского интерфейса.
Пример 3: Тест функции, выбрасывающей ошибку
struct FileHandler {
enum FileError: Error {
case fileNotFound
}
func readFile() throws -> String {
throw FileError.fileNotFound
}
}
@Test
func testReadFile() throws {
let fileHandler = FileHandler()
do {
let _ = try fileHandler.readFile()
Issue.record("Ожидалась ошибка, но не была выброшена")
} catch FileHandler.FileError.fileNotFound {
// Ожидаемая ошибка
} catch {
Issue.record("Неожиданная ошибка: \(error)")
}
}
Структура Issue в Swift предоставляет тип для описания сбоев или предупреждений, возникающих во время выполнения тестов. Эквивалентом Issue.record("Неожиданная ошибка: (error)") является XCTFail("Неожиданная ошибка: (error)")
Этот пример тестирует функцию readFile
структуры FileHandler
, которая выбрасывает ошибку FileError.fileNotFound
. Тест testReadFile
проверяет, что функция действительно выбрасывает ожидаемую ошибку и регистрирует проблему, если выброшена другая ошибка.
Организация тестов с помощью типов Suite
При управлении значительным количеством тестов важно организовать их в тестовые наборы (test suites), что значительно упрощает их выполнение и поддержку. В Swift это можно сделать двумя способами:
-
Добавление тестов в тип Swift
Просто поместите тестовые функции в тип Swift. Это автоматически создаст тестовый набор без необходимости использовать атрибут
@Suite
. Пример:struct FoodTruckTests { @Test func foodTruckExists() { ... } }
-
Использование атрибута
@Suite
Атрибут
@Suite
не обязателен, но позволяет настраивать отображение тестового набора в IDE и командной строке, а также использовать дополнительные функции, такие какtags()
для пометки тестов. Пример:@Suite("Food truck tests") struct FoodTruckTests { @Test func foodTruckExists() { ... } }
Пример использования тестовых наборов
@Suite("Food Truck Management") struct FoodTruckManagementTests {
@Test func foodTruckExists() { ... }
@Test func addFoodTruck() { ... }
}
@Suite("Customer Orders") struct CustomerOrdersTests {
@Test func orderFood() { ... }
@Test func cancelOrder() { ... }
}
В этом примере FoodTruckManagementTests
и CustomerOrdersTests
представляют собой отдельные тестовые наборы, сгруппированные по функциональным областям. Такой подход помогает легко организовать и настраивать выполнение тестов в различных сценариях.
Ограничения на типы тестовых наборов
При использовании типов в качестве тестовых наборов необходимо учитывать следующие ограничения:
-
Инициализатор типа: Если тип содержит тестовые функции, объявленные как методы экземпляра, он должен быть инициализирован с помощью инициализатора без аргументов. Этот инициализатор может быть неявным или вызываемым, синхронным или асинхронным, бросающим или не бросающим ошибки, а также иметь любой уровень доступа.
@Suite struct FoodTruckTests { var batteryLevel = 100 @Test func foodTruckExists() { ... } // ✅ ОК: Тип имеет неявный инициализатор. } @Suite struct CashRegisterTests { private init(cashOnHand: Decimal = 0.0) async throws { ... } @Test func calculateSalesTax() { ... } // ✅ ОК: Тип имеет вызываемый инициализатор. }
-
Доступность типов: Тестовые наборы и их содержимое должны быть всегда доступны, поэтому не следует использовать атрибут
@available
для ограничения их доступности.@Suite struct FoodTruckTests { ... } // ✅ OK: Тип доступен всегда. @available(macOS 11.0, *) // ❌ Ошибка: Тип может быть недоступен. @Suite struct CashRegisterTests { ... }
Наследование классов: Библиотека тестирования не поддерживает наследование между типами тестовых наборов. Поэтому классы, используемые как тестовые наборы, должны быть объявлены как
final
.
@Suite final class FoodTruckTests { ... } // ✅ ОК: Класс объявлен как final.
actor CashRegisterTests: NSObject { ... } // ✅ ОК: Акторы по умолчанию являются final.
class MenuItemTests { ... } // ❌ Ошибка: Этот класс не объявлен как final.
Использование .tags для ограничения выполнения теста
В Swift Testing для организации и ограничения выполнения тестов можно использовать теги (tags
). Это мощный инструмент для классификации тестов и управления их выполнением в различных сценариях. Теги могут применяться как к отдельным тестовым функциям, так и к целым тестовым наборам.
Основные принципы использования тегов
-
Определение тегов
Теги определяются с использованием статического расширения структуры
Tag
. Это позволяет задать статические свойства, представляющие различные категории тестов:extension Tag { @Tag static var regression: Self @Tag static var ui: Self @Tag static var performance: Self // Дополнительные теги можно добавлять по мере необходимости }
В этом примере определены теги для регрессионного тестирования (
regression
), тестирования пользовательского интерфейса (ui
) и производительности (performance
). -
Аннотация тестовых функций
Теги могут быть применены к тестовым функциям с использованием атрибута
@Test
. Это делает возможным указать, какие категории тестирования относятся к конкретной тестовой функции:@Test(.tags(.regression, .ui)) func testUserInterface() { // Логика теста для проверки пользовательского интерфейса }
В данном примере тест
testUserInterface
относится к категориямregression
иui
, что означает, что он может быть запущен как при выполнении регрессионных тестов, так и при проверке пользовательского интерфейса. -
Аннотация тестовых наборов
Теги также могут применяться ко всему тестовому набору, используя атрибут
@Suite
. Это позволяет легко классифицировать и ограничивать выполнение всех тестов внутри набора:@Suite(.tags(.performance)) struct PerformanceTests { @Test func testAppLaunchSpeed() { // Логика теста для проверки скорости запуска приложения } @Test func testMemoryUsage() { // Логика теста для проверки использования памяти } }
В этом случае все тесты в наборе
PerformanceTests
автоматически наследуют тегperformance
, что позволяет запускать их только при необходимости выполнения тестов производительности.
Примеры использования и пояснения
-
Комбинация тегов
Теги могут комбинироваться для точной классификации тестов:
@Test(.tags(.regression, .critical)) func testCriticalFunctionality() { // Логика теста для критически важной функциональности }
В этом примере тест
testCriticalFunctionality
помечен как критически важный и подлежащий проверке в регрессионных тестах. -
Динамическое управление тегами
Теги могут также использоваться для динамического управления тестированием в зависимости от окружения или стадии разработки:
#if DEBUG @Test(.tags(.fast)) #else @Test(.tags(.slow, .full)) #endif func testComplexCalculation() { // Логика теста для сложных вычислений }
В этом примере тест
testComplexCalculation
отмечен тегомfast
в режиме отладки и тегамиslow
иfull
в релизном режиме, что позволяет выбирать, какие тесты запускать в зависимости от текущего окружения.
Использование тегов в тестировании Swift обеспечивает гибкость и удобство в организации и выполнении тестов, позволяя легко управлять их выполнением в различных условиях и сценариях разработки.
Миграция тестов из XCTest в библиотеку Testing
Переход с использования XCTest на новую библиотеку Testing в Swift требует выполнения нескольких ключевых шагов, включая импорт библиотеки, изменение структуры тестовых классов и использование новых методов для проверок условий. Давайте рассмотрим каждый из этих шагов подробнее.
Импорт библиотеки Testing
Первым шагом является замена импорта библиотеки XCTest на библиотеку Testing в ваших тестовых файлах Swift:
import Testing
Этот импорт должен быть добавлен в файлы, где вы определяете ваши тесты. Важно помнить, что библиотеку Testing нужно использовать именно в контексте тестов. Импорт этой библиотеки в основное приложение или библиотеку не поддерживается и не рекомендуется.
Примеры миграции тестов
Конвертация классов тестов
В XCTest тесты обычно группируются внутри классов, которые наследуются от XCTestCase
. В библиотеке Testing тесты могут быть объявлены как свободные функции, статические методы структур или классов, или как члены структур с атрибутом @Test
.
Пример миграции класса тестов из XCTest в структуру с атрибутом @Test
:
До (XCTest):
class FoodTruckTests: XCTestCase {
func testEngineWorks() {
// Тестовая логика здесь.
}
}
После (Testing):
struct FoodTruckTests {
@Test func engineWorks() {
// Тестовая логика здесь.
}
}
Преобразование setup и teardown функций
В XCTest используются методы setUp()
и tearDown()
для выполнения кода перед и после выполнения каждого теста. В библиотеке Testing аналогичное поведение можно реализовать с помощью инициализаторов и деинициализаторов структур или классов.
Пример преобразования setup и teardown из XCTest в инициализатор и деинициализатор:
До (XCTest):
class FoodTruckTests: XCTestCase {
var batteryLevel: NSNumber!
override func setUp() {
batteryLevel = 100
}
override func tearDown() {
batteryLevel = 0
}
}
После (Testing):
struct FoodTruckTests {
var batteryLevel: NSNumber
init() {
batteryLevel = 100
}
deinit {
batteryLevel = 0
}
}
Преобразование тестовых методов
В XCTest тестовый метод должен быть методом класса XCTestCase
и начинаться с префикса test
. В библиотеке Testing тестовые функции идентифицируются с помощью атрибута @Test
.
Пример преобразования тестового метода из XCTest в функцию с атрибутом @Test
:
До (XCTest):
class FoodTruckTests: XCTestCase {
func testEngineWorks() {
// Тестовая логика здесь.
}
}
После (Testing):
struct FoodTruckTests {
@Test func engineWorks() {
// Тестовая логика здесь.
}
}
Сравнение функций XCTAssert и expect/require
XCTest предоставляет набор функций XCTAssert()
для проверки условий в тестах. В библиотеке Testing альтернативными являются макросы expect(::sourceLocation:)
и require(::sourceLocation:)
. Эти макросы обеспечивают схожее поведение с XCTAssert()
, однако require(::sourceLocation:)
выбрасывает ошибку, если условие не выполняется.
Примеры использования Assert в XCTest и эквиваленты в Testing:
-
XCTest:
class FoodTruckTests: XCTestCase { func testEngineWorks() throws { let engine = FoodTruck.shared.engine XCTAssertNotNil(engine.parts.first) XCTAssertGreaterThan(engine.batteryLevel, 0) try engine.start() XCTAssertTrue(engine.isRunning) } }
-
Testing:
struct FoodTruckTests { @Test func engineWorks() throws { let engine = FoodTruck.shared.engine try #require(engine.parts.first != nil) #expect(engine.batteryLevel > 0) try engine.start() #expect(engine.isRunning) } }
Таблица соответствий функций XCTAssert и expect/require
XCTest |
swift-testing |
---|---|
XCTAssert(x), XCTAssertTrue(x) |
#expect(x) |
XCTAssertFalse(x) |
#expect(!x) |
XCTAssertNil(x) |
#expect(x == nil) |
XCTAssertNotNil(x) |
#expect(x != nil) |
XCTAssertEqual(x, y) |
#expect(x == y) |
XCTAssertNotEqual(x, y) |
#expect(x != y) |
XCTAssertIdentical(x, y) |
#expect(x === y) |
XCTAssertNotIdentical(x, y) |
#expect(x !== y) |
XCTAssertGreaterThan(x, y) |
#expect(x > y) |
XCTAssertGreaterThanOrEqual(x, y) |
#expect(x >= y) |
XCTAssertLessThanOrEqual(x, y) |
#expect(x <= y) |
XCTAssertLessThan(x, y) |
#expect(x < y) |
XCTAssertThrowsError(try f()) |
#expect(throws: (any Error).self) { try f() } |
XCTAssertThrowsError(try f()) { error in … } |
#expect { try f() } throws: { error in return … } |
XCTAssertNoThrow(try f()) |
#expect(throws: Never.self) { try f() } |
try XCTUnwrap(x) |
try #require(x) |
XCTFail("…") |
Issue.record("…") |
Эта таблица поможет вам перейти от использования функций XCTest к эквивалентным методам в библиотеке Testing, обеспечивая при этом совместимость и сохранение функциональности ваших тестов.
Проверка ожидаемых значений и результатов в Swift тестировании
В процессе написания тестов на Swift, особенно при использовании библиотеки Testing, важно уметь эффективно проверять ожидаемые значения и результаты выполнения операций. Для этого используются функции expect
и require
, каждая из которых имеет свои особенности и предназначена для различных сценариев тестирования.
Использование expect
Функция expect
предназначена для проверки условий в тестах. Она сообщает о неудаче, если условие не выполняется, но позволяет тесту продолжать своё выполнение.
Пример использования expect
:
@Test func calculatingOrderTotal() {
let calculator = OrderCalculator()
#expect(calculator.total(of: [3, 3]) == 6)
// Выведет "Expectation failed: (calculator.total(of: [3, 3]) → 6) == 7"
}
В данном примере, если сумма расчёта не равна 6, тест продолжит выполнение, но фиксируется ошибка, которая будет отображена в выводе тестов.
Использование require
Функция require
работает аналогично expect
, но с одним важным отличием: если условие не выполняется, тест немедленно завершается с ошибкой ExpectationFailedError
.
Пример использования require
:
@Test func returningCustomerRemembersUsualOrder() throws {
let customer = try #require(Customer(id: 123))
// Тест не продолжится, если customer равен nil.
#expect(customer.usualOrder.countOfItems == 2)
}
Здесь, если объект customer
равен nil, тест будет остановлен, и ошибку можно будет увидеть в выводе тестов.
Проверка опциональных значений
Для проверки опциональных значений в XCTest используется функция XCTUnwrap
, которая бросает ошибку, если значение nil. В библиотеке Testing аналогичную функциональность предоставляет require
.
Пример с использованием require
:
struct FoodTruckTests {
@Test func engineWorks() throws {
let engine = FoodTruck.shared.engine
let part = try #require(engine.parts.first)
// Далее идёт код, который зависит от наличия `part`
}
}
Если engine.parts.first
равно nil, то будет выброшено исключение, и тест будет прерван.
Запись ошибок
В XCTest для записи ошибок, которые должны приводить к неудачному завершению теста в любом случае, используется функция XCTFail
. В библиотеке Testing аналогичной функцией является Issue.record
.
Пример с использованием Issue.record
:
struct FoodTruckTests {
@Test func engineWorks() {
let engine = FoodTruck.shared.engine
guard case .electric = engine else {
Issue.record("Engine is not electric")
return
}
// Далее идёт код, зависящий от того, что `engine` является электрическим
}
}
Эта функция используется для фиксации ошибок или проблем, которые должны быть отмечены в тестовом выводе, но при этом не приводят к немедленному прерыванию выполнения теста.
Тестирование асинхронного поведения
Для тестирования асинхронного поведения в Swift, особенно при использовании Confirmation
, важно следовать нескольким ключевым шагам и понимать принципы работы этого подхода. Давайте подробнее рассмотрим основные моменты и примеры использования.
Использование Confirmation для проверки асинхронных событий
Создание Confirmation
Для начала нужно создать Confirmation внутри тестовой функции с помощью функции confirmation()
из библиотеки Testing. Confirmation создаётся с ожидаемым числом событий и замыканием, которое будет вызвано, когда условие будет выполнено.
Примеры использования
-
Пример: Проверка выполнения асинхронной задачи
@Test func asyncTaskCompletion() async { await confirmation("Task should complete") { taskCompleted in Task { await performAsyncTask() taskCompleted() } } }
В этом примере
confirmation
ожидает завершение асинхронной задачиperformAsyncTask
. Когда задача завершается, вызываетсяtaskCompleted()
, подтверждающее выполнение задачи. -
Пример: Проверка асинхронного события
@Test func eventHandlingTest() async { await confirmation("Event should be handled") { eventHandled in EventManager.shared.eventHandler = { event in if event.type == .desiredEvent { eventHandled() } } EventManager.shared.triggerEvent(.desiredEvent) } }
В этом случае мы ожидаем, что событие
.desiredEvent
будет обработано вEventManager
. Когда событие обрабатывается, вызываетсяeventHandled()
, что подтверждает обработку события. -
Пример: Проверка отсутствия асинхронного события
@Test func orderCalculatorEncountersNoErrors() async { let calculator = OrderCalculator() await confirmation(expectedCount: 0) { confirmation in calculator.errorHandler = { _ in confirmation() } calculator.subtotal(for: PizzaToppings(bases: [])) } }
Чтобы убедиться, что определенное событие не происходит во время теста, создайте объект
Confirmation
с ожидаемым количеством0
.
Контроль выполнения тестов
ConditionTrait в тестировании Swift предоставляет гибкий механизм для контроля выполнения тестов на основе определённых условий. Это особенно полезно для ситуаций, когда нужно пропускать или выполнять тесты в зависимости от различных внешних или внутренних условий приложения или окружения.
Основные принципы использования ConditionTrait
-
Пропуск тестов на основе условий
Пример, показанный ниже, демонстрирует использование
@Suite
и@Test
с атрибутами ConditionTrait:@Suite(.disabled(if: CashRegister.isEmpty)) struct CashRegisterTests { @Test(.enabled(if: CashRegister.hasMoney)) func testCashRegisterOperation() { // Логика теста } }
@Suite(.disabled(if: CashRegister.isEmpty))
: Этот атрибут говорит о том, что все тесты в набореCashRegisterTests
будут пропущены, еслиCashRegister.isEmpty
вернётtrue
. Это может быть полезно, если тесты касаются операций с кассой, которая должна содержать деньги для корректного тестирования.@Test(.enabled(if: CashRegister.hasMoney))
: Этот атрибут применяется к конкретному тестуtestCashRegisterOperation
и указывает, что тест будет выполняться только еслиCashRegister.hasMoney
вернётtrue
. Это позволяет исключить выполнение теста, если требуемые условия не выполнены.
-
Проверка различных условий
ConditionTrait может использоваться для проверки различных условий, таких как:
Наличие определённых данных в приложении.
Определённое состояние системы или базы данных.
Версия операционной системы или другие системные характеристики.
Дополнительные примеры
Пропуск тестов на основе наличия элементов в меню
@Suite struct MenuTests {
@Test(.enabled(if: Menu.hasItem(.pizza)))
func testPizzaOrder() {
// Логика теста для заказа пиццы
}
@Test(.enabled(if: Menu.hasItem(.sushi)))
func testSushiOrder() {
// Логика теста для заказа суши
}
}
@Test(.enabled(if: Menu.hasItem(.pizza)))
: Этот тестtestPizzaOrder
будет выполняться только если в меню присутствует элементpizza
.@Test(.enabled(if: Menu.hasItem(.sushi)))
: Этот тестtestSushiOrder
будет выполняться только если в меню присутствует элементsushi
.
Такой подход позволяет автоматизировать тестирование приложения, учитывая разнообразные условия, которые могут влиять на его функциональность и требования к тестам.
Аннотация известных проблем
Аннотирование известных проблем в тестах с помощью функции withKnownIssue
предоставляет мощный инструмент для управления и отслеживания известных проблем во время выполнения тестов. Давайте подробнее рассмотрим примеры использования и особенности этой функции.
Пример использования withKnownIssue
Простое аннотирование проблемы
struct FoodTruckTests {
@Test func grillWorks() async {
withKnownIssue("Grill is out of fuel") {
try FoodTruck.shared.grill.start()
}
// Дополнительные проверки и логика теста
}
}
В этом примере тест grillWorks
помечен как имеющий известную проблему — "Grill is out of fuel". Если при выполнении теста возникнет ошибка, связанная с тем, что гриль не может быть запущен из-за отсутствия топлива, тест не будет считаться проваленным.
Указание интермиттирующих ошибок
Иногда известные проблемы могут проявляться не всегда, а только в определенных условиях. Для таких случаев функцию withKnownIssue
можно настроить с флагом isIntermittent
.
struct FoodTruckTests {
@Test func grillWorks() async {
withKnownIssue("Grill may need fuel", isIntermittent: true) {
try FoodTruck.shared.grill.start()
}
// Дополнительные проверки и логика теста
}
}
В этом примере проблема с отсутствием топлива в гриле помечена как интермиттирующая. Это означает, что тесты должны быть готовы к тому, что ошибка может возникать не всегда, а только в определенных условиях.
Условия и сопоставление проблем
Функция withKnownIssue
также позволяет задавать условия, при которых известная проблема считается актуальной, и сопоставлять ошибки с определенными критериями.
struct FoodTruckTests {
@Test func grillWorks() async {
withKnownIssue("Grill is out of fuel") {
try FoodTruck.shared.grill.start()
} when: {
FoodTruck.shared.hasGrill
} matching: { issue in
issue.error != nil
}
// Дополнительные проверки и логика теста
}
}
Здесь указано, что проблема с отсутствием топлива в гриле актуальна только при условии, что гриль установлен (FoodTruck.shared.hasGrill
). Также проверяется, что в объекте issue
есть ошибка (issue.error != nil
), чтобы считать проблему действительной.
Заключение
Тестирование в Swift играет ключевую роль в обеспечении качества программного обеспечения. От юнит-тестов до системных проверок, тесты помогают разработчикам уверенно вносить изменения и поддерживать стабильность кода. XCTest, как стандартный фреймворк, обеспечивает базовые возможности для написания и запуска тестов. Однако новая библиотека Testing предлагает удобные синтаксические конструкции и дополнительные функции, которые делают процесс тестирования еще более гибким и мощным.
Мы рассмотрели еще не все фичи Swift Testing, так что давайте будем экспериментировать и дальше! А я пошел дальше смотреть видео с WWDC.
FreeNickname
Скажите, пожалуйста, можно ли использовать Swift Testing на старых версиях iOS? Ну то есть если target-ом выбран, например, симулятор с iOS 14, и deployment target приложения iOS 14, оно будет работать?
chesnikovofficial Автор
Если судить по package.swift, то поддерживаются
.macOS(.v10_15),
.iOS(.v13),
.watchOS(.v6),
.tvOS(.v13),
.macCatalyst(.v13),
.visionOS(.v1),
В доке на сайте Apple же нам дает требования Swift 6.0+, Xcode 16.0+
Так что, по идее ваш кейс будет работать!
FreeNickname
О, отлично! Значит, можно начинать пользоваться) Спасибо!