Одной из замечательных особенностей разработки в SwiftUI является Xcode Previews, которые обеспечивают быструю UI‑итерацию путем визуализации изменений кода в режиме реального времени наряду с кодом SwiftUI. В DoorDash мы активно используем Xcode Previews вместе с библиотекой SnapshotTesting от Point‑Free, чтобы убедиться, что экраны выглядят так, как мы ожидаем, при их разработке, и гарантировать, что они не изменятся неожиданным образом с течением времени.
SnapshotTesting можно использовать для захвата визуализированного изображения VIEW и создания XCTest — сбоя, если новое изображение не соответствует эталонному изображению на диске. Xcode Previews в сочетании с SnapshotTesting можно использовать для обеспечения быстрых итераций, при этом гарантируя, что вью продолжают выглядеть так, как они задуманы, не опасаясь неожиданных изменений.
Трудность совместного использования Xcode Previews и SnapshotTesting заключается в том, что это может привести к большому количеству шаблонов и дублированию кода между превью и тестами. Чтобы решить эту проблему, инженеры DoorDash разработали PreviewSnapshots, инструмент предварительного просмотра снапшотов, с открытым исходным кодом, который можно использовать для простого обмена конфигураций между превью Xcode и тестами снапшотов. В этой статье мы тщательно исследуем эту тему, сначала предоставив некоторые сведения о том, как работают Xcode‑превью и SnapshotTesting, а затем объясним, как использовать новый опенсорс‑инструмент, с пояснительными примерами того, как исключить дублирование кода с помощью передачи конфигураций вью между превью и снапшотами.
Как работают Xcode Previews
Xcode Previews позволяют разработчикам возвращать одну или несколько версий View из PreviewProvider, а Xcode визуализирует «живую» версию View вместе с кодом реализации.
Начиная с Xcode 14 вью с несколькими превью представлены в виде выбираемых вкладок в верхней части окна превью, как показано на рисунке 1.
Как работает SnapshotTesting
Библиотека SnapshotTesting позволяет разработчикам писать тестовые утверждения о внешнем виде их вью. Утверждая(*заявляя), что вью соответствует эталонным изображениям на диске, разработчики могут быть уверены, что со временем вью не изменятся неожиданным образом.
Пример кода на рис. 2 сравнивает короткую и длинную версии MessageView с эталонными изображениями, хранящимися на диске как testSnapshots.1 и testSnapshots.2 соответственно. Первоначально снапшоты были записаны с помощью SnapshotTesting и автоматически названы по имени тестовой функции и позиции утверждения внутри функции.
Проблема совместного использования Xcode Previews и SnapshotTesting
Между кодом, используемым для Xcode Previews, и кодом для создания тестов снапшотов есть много общего. Это сходство может привести к дублированию кода и дополнительным усилиям разработчиков в попытке охватить обе технологии. В идеале разработчики могли бы написать код для предпросмотра вью в различных конфигурациях, а затем повторно использовать этот код для тестирования снапшотов вью в тех же самых конфигурациях.
Представляем PreviewSnapshots
PreviewSnapshots может помочь решить эту проблему дублирования кода. PreviewSnapshots позволяет разработчикам создавать единый набор состояний вью для Xcode Previews и создавать примеры тестирования снапшотов для каждого из состояний с одним тестовым утверждением. Ниже мы рассмотрим, как это работает, на простом примере.
Использование PreviewSnapshots для простого вью
Допустим, у нас есть вью, которое принимает список имен и отображает их каким‑то интересным нам способом.
Традиционно мы хотели бы создать превью для нескольких интересующих нас состояний данного вью. Может быть: пусто, одно имя, короткий список имен и длинный список имен.
struct NameList_Previews: PreviewProvider { static var previews: some View { NameList(names: []) .previewDisplayName("Empty") .previewLayout(.sizeThatFits) NameList(names: [“Alice”]) .previewDisplayName("Single Name") .previewLayout(.sizeThatFits) NameList(names: [“Alice”, “Bob”, “Charlie”]) .previewDisplayName("Short List") .previewLayout(.sizeThatFits) NameList(names: [ “Alice”, “Bob”, “Charlie”, “David”, “Erin”, //... ]) .previewDisplayName("Long List") .previewLayout(.sizeThatFits) } }
Далее мы написали бы очень похожий код для тестирования снапшотов.
final class NameList_SnapshotTests: XCTestCase { func test_snapshotEmpty() { let view = NameList(names: []) assertSnapshot(matching: view, as: .image) } func test_snapshotSingleName() { let view = NameList(names: [“Alice”]) assertSnapshot(matching: view, as: .image) } func test_snapshotShortList() { let view = NameList(names: [“Alice”, “Bob”, “Charlie”]) assertSnapshot(matching: view, as: .image) } func test_snapshotLongList() { let view = NameList(names: [ “Alice”, “Bob”, “Charlie”, “David”, “Erin”, //... ]) assertSnapshot(matching: view, as: .image) } }
Длинный список именпотенциально может быть распределен между превью и тестированием снапшотов с помощью статического свойства, но при этом избежать написания вручную отдельного теста снапшотов для каждого просматриваемого состояния не получится.
PreviewSnapshots позволяет разработчикам определить единую коллекцию интересующих конфигураций, а затем тривиально повторно использовать их между превью и тестами снапшотов.
Так выглядит Xcode превью с использованием PreviewSnapshots:
struct NameList_Previews: PreviewProvider { static var previews: some View { snapshots.previews.previewLayout(.sizeThatFits) } static var snapshots: PreviewSnapshots<[String]> { PreviewSnapshots( configurations: [ .init(name: "Empty", state: []), .init(name: "Single Name", state: [“Alice”]), .init(name: "Short List", state: [“Alice”, “Bob”, “Charlie”]), .init(name: "Long List", state: [ “Alice”, “Bob”, “Charlie”, “David”, “Erin”, //... ]), ], configure: { names in NameList(names: names) } ) } }
Чтобы создать коллекцию PreviewSnapshots, мы создаем экземпляр PreviewSnapshots с массивом конфигураций вместе с функцией configure для настройки вью для данной конфигурации. Конфигурация состоит из имени и экземпляра State, которое будет использоваться для настройки представления. В этом случае тип состояния для массива имен будет [String].
Для создания превью мы возвращаем snapshots.previews из стандартного статического свойства превью, как показано на рис. 3. snapshots.previews создаст превью с правильным именем для каждой конфигурации PreviewSnapshots.
PreviewSnapshots обеспечивает некоторую дополнительную структуру для небольшого вью, построить которое несложно, но мало что делает для сокращения количества строк кода в превью. Основное преимущество небольших вью проявляется, когда приходит время писать тесты снапшотов для превью.
final class NameList_SnapshotTests: XCTestCase { func test_snapshot() { NameList_Previews.snapshots.assertSnapshots() } }
Это единственное утверждение выполнит снапшот‑тест каждой конфигурации в PreviewSnapshots. На рис. 4 показан код примера вместе с эталонными изображениями в Xcode. Кроме того, если в превью будут добавлены какие‑либо новые конфигурации, к ним автоматически будет применен снапшот‑тест без изменения тестового кода.
Для более сложных вью с большим количеством аргументов — еще больше преимуществ.
Использование PreviewSnapshots для более сложного вью
Во втором примере мы рассмотрим FormView, который принимает несколько Bindings, опциональное сообщение об ошибке и замыкание действия в качестве аргументов в своем инициализаторе. Этот пример покажет возросшие преимущества PreviewSnapshots в ситуации, когда увеличивается сложность построения вью.
struct FormView: View { init( firstName: Binding<String>, lastName: Binding<String>, email: Binding<String>, errorMessage: String?, submitTapped: @escaping () -> Void ) { ... } // ... }
Поскольку PreviewSnapshots является дженериком для состояния ввода, мы можем объединить различные входные параметры в небольшую вспомогательную структуру для передачи в блок configure, и лишь один раз нужно будет cоставить FormView. В качестве дополнительного удобства PreviewSnapshots предоставляет протокол NamedPreviewState для упрощения создания конфигураций входа путем группировки имени превью вместе(*в соответствии) с состоянием превью.
struct FormView_Previews: PreviewProvider { static var previews: some View { snapshots.previews } static var snapshots: PreviewSnapshots<PreviewState> { PreviewSnapshots( states: [ .init(name: "Empty"), .init( name: "Filled", firstName: "John", lastName: "Doe", email: "john.doe@doordash.com" ), .init( name: "Error", firstName: "John", lastName: "Doe", errorMessage: "Email Address is required" ), ], configure: { state in NavigationView { FormView( firstName: .constant(state.firstName), lastName: .constant(state.lastName), email: .constant(state.email), errorMessage: state.errorMessage, submitTapped: {} ) } } ) } struct PreviewState: NamedPreviewState { let name: String var firstName: String = "" var lastName: String = "" var email: String = "" var errorMessage: String? } }
В коде примера мы создали структуру PreviewState, соответствующую по форме NamedPreviewState и содержащую имя превью наряду с именем, фамилией, адресом электронной почты и опциональным сообщением об ошибке для создания вью. Затем, на основе переданного состояния конфигурации, в блоке configure мы создаем один экземпляр FormView.
Возвращая snapshots.previewиз PreviewProvider.previews, PreviewSnapshots будет перебирать входные состояния и создаст превью Xcode с надлежащим именем для каждого состояния, как показано на рисунке 5.
После того, как мы определили набор PreviewSnapshots для превью, мы снова можем создать набор снапшот-тестов с единственным утверждением юнит-теста.
final class FormView_SnapshotTests: XCTestCase { func test_snapshot() { FormView_Previews.snapshots.assertSnapshots() } }
Как и в приведенном выше более простом примере, этот тестовый пример будет сравнивать каждое из состояний превью, определенных в FormView_Previews.snapshots, с эталонным изображением, записанным на диск, и генерировать сбой теста, если изображения не соответствуют ожиданиям.
Заключение
В этой статье обсуждались определенные преимущества использования Xcode Previews и SnapshotTesting при разработке с помощью SwiftUI. Он также продемонстрировал некоторые болевые точки и дублирование кода, которые могут возникнуть в результате совместного использования этих двух технологий, и то, как PreviewSnapshots позволяет разработчикам сэкономить время, повторно используя усилия, которые они вложили в написание превью Xcode для тестирования снапшотов.
Инструкции по включению PreviewSnapshots в ваш проект, а также пример приложения, использующего PreviewSnapshots, доступны на GitHub.
ссылка на оригинал статьи https://habr.com/ru/post/713346/
Добавить комментарий