
Пару месяцев назад Стюарт Монтгомери, программист из команды XCTest в Apple, поделился новой библиотекой тестирования Swift с открытым исходным кодом на основе макросов.
Библиотека называется Swift-testing, и, как указано в ее документации, она предназначена для проверки концепции нового API тестирования для Swift, основанного на макросах и интегрированного в Swift так же, как и XCTest.
По этой причине предполагается, что библиотека будет недолговечной и не предназначенной для использования на продакшене или миграции из XCTest. Тем не менее, это отличный способ получить представление о том, как может выглядеть будущее тестирования в Swift, что я и сделал.
Начнем
Первое, что я сделал, — это создал небольшой swift package, который позволил бы мне написать несколько тестов с использованием новой библиотеки:
import PackageDescription let package = Package( name: "SwiftTestingDemo", products: [ .library( name: "SwiftTestingDemo", targets: ["SwiftTestingDemo"]), ], dependencies: [ ], targets: [ .target( name: "SwiftTestingDemo", swiftSettings: [ .enableUpcomingFeature("BareSlashRegexLiterals") ] ) ] )
Затем я добавил код из своего приложения QReate, который декодирует строки URL-адреса WIFI в структуры Swift, что, по моему мнению, было бы идеальным кандидатом для проверки swift-testing:
import Foundation struct WifiNetwork { let ssid: String let password: String let security: String? let hidden: String? } protocol ErrorMonitoring { func monitor(_ error: Error) } struct WifiParser { enum Error: Swift.Error { case noMatches } private let monitoring: ErrorMonitoring init(monitoring: ErrorMonitoring) { self.monitoring = monitoring } func parse(wifi: String) throws -> WifiNetwork { let regex = /WIFI:S:(?<ssid>[^;]+);(?:T:(?<security>[^;]*);)?P:(?<password>[^;]+);(?:H:(?<hidden>[^;]*);)?;/ guard let result = try? regex.wholeMatch(in: wifi) else { let error = Error.noMatches monitoring.monitor(error) throw error } return WifiNetwork( ssid: String(result.ssid), password: String(result.password), security: result.security.map(String.init), hidden: result.hidden.map(String.init) ) } }
Настройка swift-testing
Теперь, когда у меня был код для тестирования, я пошел дальше и настроил цель тестирования, чтобы иметь возможность начать использовать swift-testing.
Подключаем swift-testing
Первое, что мне нужно было сделать, — это добавить swift-testing в качестве зависимости как к пакету, так и к цели тестирования:
import PackageDescription let package = Package( name: "SwiftTestingDemo", platforms: [ .macOS(.v13), .iOS(.v16), .watchOS(.v9), .tvOS(.v16), .visionOS(.v1) ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "SwiftTestingDemo", targets: ["SwiftTestingDemo"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-testing.git", branch: "main"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( name: "SwiftTestingDemo", swiftSettings: [ .enableUpcomingFeature("BareSlashRegexLiterals") ] ), .testTarget( name: "SwiftTestingDemoTests", dependencies: ["SwiftTestingDemo", .product(name: "Testing", package: "swift-testing")]), ] )
Запускаем тесты
Из-за особенностей организации тестов в swift-testing и того факта, что оно еще не интегрировано в Xcode или SPM, вам необходимо создать небольшой подкласс XCTestCase, создающий единый тестовый метод, который находит и запускает все тесты с помощью XCTestScaffold:
import XCTest final class AllTests: XCTestCase { func testAll() async { await XCTestScaffold.runAllTests(hostedBy: self) } }
Добавим тесты
Теперь, когда я правильно настроил цель теста, я могу приступить к написанию тестов. На основе своего кода я решил написать три отдельных теста:
-
Тест, который проверяет, может ли парсер распарсить строку WIFI со всеми присутствующими полями.
-
Тест, проверяющий, что парсер выдает ошибку, когда строка WIFI недействительна.
-
Тест, который проверяет, может ли парсер распарсить строку WIFI, в которой присутствуют только обязательные поля.
Перед реализацией
Подход, к тому, как вы пишите тесты изменился в сравнении с XCTest. Вам больше не нужно добавлять к функциям префикс test, и теперь вместо этого вы можете прикрепить макрос @Test к любой функции, которую вы хотите использовать в качестве теста:
@Test func whenParseIsCalledWithAllFields_ThenNetworkIsInitialisedCorrectly() { // ... }
Организация тестов
Благодаря этому новому подходу к тестированию у вас появляется гораздо больше свободы в организации тестов. Вы можете либо объявить их все как глобальные функции, либо инкапсулировать их по назначению в структуру или final класс.
@Suite struct WifiParserTests { func whenParseIsCalledWithAllFields_ThenNetworkIsInitialisedCorrectly() { // ... } }
Из того, что я понял, прочитав документацию, кажется хорошей практикой прикреплять макрос @Suite к структурам, которые группируют тесты.
Тест на неверную строку WIFI
Первый тест, который я хотел добавить и который продемонстрировал бы различия между swift-testing и XCTest, заключался в проверке того, что парсер выдает ошибку и отправляет событие мониторинга, когда строка WIFI неверная:
@Suite struct WifiParserTests { let sut: WifiParser let errorMonitoring: SpyErrorMonitoring // 1 init() async throws { errorMonitoring = SpyErrorMonitoring() sut = WifiParser(monitoring: errorMonitoring) } func whenParseIsCalledWithWrongString_ThenNoMatchesErrorIsThrownAndMonitoringEventIsSent() { // 2 #expect(throws: WifiParser.Error.noMatches) { try sut.parse(wifi: "") } // 3 #expect(errorMonitoring.capturedErrors.compactMap { $0 as? WifiParser.Error } == [.noMatches]) } }
Давайте разберем приведенный выше набор тестов и поймем, что происходит:
-
Первое, что я сделал, — это настроил тестируемую систему (
WifiParser) и тестовый дублер для отслеживания событий мониторинга (SpyErrorMonitoring). В XCTest это обычно делается в методеsetUp, чтобы экземпляры создавались перед каждым тестом. Однако в swift-testing, поскольку пакет создается перед запуском каждого теста, достаточно сделать это в методеinit. -
Использовал перегрузку макроса
#expect, которая позволяет убедиться, что замыкание вызывает определенную ошибку. Можно передать тип вместо экземпляра ошибки, чтобы не углубляться в конкретизацию. -
Использовал макрос
#expectс логическим условием, чтобы подтвердить, что дублер зафиксировал правильную ошибку.
Тест со всеми полями
Второй тест, который я написал, проверял, что парсер может проанализировать валидную строку WIFI со всеми присутствующими полями:
@Suite struct WifiParserTests { func whenParseIsCalledWithAllFields_ThenNetworkIsInitialisedCorrectly() throws { let wifi = "WIFI:S:superwificonnection;T:WPA;P:strongpassword;H:YES;;" let network = try sut.parse(wifi: wifi) #expect(try #require(network.security) == "WPA") #expect(try #require(network.hidden) == "YES") #expect(network.ssid == "superwificonnection") #expect(network.password == "strongpassword") } }
Как вы можете видеть в приведенном выше примере, аналогично тому, как вы пишете тесты в XCTest, вы все равно можете объявить методы как генерирующие исключение (и даже async), но вместо выделенных методов XCTAssert у вас теперь есть один макрос #expect. который вы можете использовать, чтобы утверждать, что условие истинно.
Вдобавок ко всему, как вы можете видеть выше, где я разворачиваю необязательный параметр security и hidden, вместо использования XCTUnwrap можно использовать макрос #require.
Тест только с обязательными полями
Последний тест, который я добавил, заключался в проверке того, что моя логика будет работать, даже если строка Wi-Fi содержит только обязательные поля, используя только макрос #expect:
@Suite struct WifiParserTests { func whenParseIsCalledWithStringContainingOnlyRequiredFields_ThenCorrectValuesAreReturned() throws { let wifi = "WIFI:S:superwificonnection;P:strongpassword;;" let network = try sut.parse(wifi: wifi) #expect(network.hidden == nil) #expect(network.security == nil) #expect(network.ssid == "superwificonnection") #expect(network.password == "strongpassword") } }
Что почитать далее
Приведенные выше примеры — лишь небольшая часть того, что вы можете сделать с помощью swift-testing. Если вы пробуете библиотеку и пытаетесь выяснить, как выполнить конкретную проверку, которую вы обычно делаете в XCTest, я рекомендую вам просмотреть эту диаграмму миграции из документации, которая сравнивает обе библиотеки:
Примеры и более подробное описание самой библиотеки можно найти в ее документации, которая размещена на сайте.
Перевод статьи подготовлен в преддверии старта специализации IOS Developer. На странице специализации вы можете зарегистрироваться на бесплатные вебинары и интенсив курса.
ссылка на оригинал статьи https://habr.com/ru/articles/790792/
Добавить комментарий