Автоматическое тестирование приложений
Улучшаем предварительные просмотры Xcode с помощью покрытия модульными тестами
Написание тестов всегда кажется худшей частью тестирования, а это простой способ получить большое тестовое покрытие без необходимости писать какие-либо тесты!
В этом году предварительные просмотры Xcode получили большое обновление благодаря новому движку выполнения в Xcode 16. Apple предоставила нам множество новых функций, связанных с предварительным просмотром, таких как поддержка UIKit в макросах #Preview, @Previewable для управления состоянием и PreviewModifier для повторного использования данных. Все это упрощает внедрение предварительных просмотров и повышает популярность разработки с использованием предварительных просмотров.
Однако предварительный просмотр может быть подвержен неожиданным сбоям. Например, кто-то из вашей команды добавляет свойство в представление, использующее @EnvironmentObject
. Если он забудет обновить каждый вызов, чтобы установить окружение, приложение упадет. Любой предварительный просмотр, использующий представление без environment, также будет аварийно завершен, когда Xcode попытается отобразить предварительный просмотр. Эта проблема еще более сложная, потому что в Xcode нет встроенного способа проверить, что превью не упадут, без ручного тестирования каждого из них — а это заняло бы невероятно много времени.
Некоторые компании, например Doordash и Handshake, писали о том, как они используют предварительные просмотры для своих снэпшот-тестов — это одна из техник, позволяющих отлавливать неработающие предварительные просмотры. В этом посте мы рассмотрим еще более простой способ — написание юнит-теста, который делает проход по макету всех превью, используя Swift-пакет SnapshotPreviews от Emerge с открытым исходным кодом.
Пример
Рассмотрим эти два представления в разных файлах:
// Title.swift struct Title: View { let title: String var body: some View { Text(title) .font(.largeTitle) } } #Preview { Title(title: "Title") } // TitleSubtitle.swift struct TitleSubtitle: View { let title: String let subtitle: String var body: some View { HStack { Title(title: title) Text(subtitle) } } } #Preview { TitleSubtitle(title: "Title", subtitle: "subtitle") }
Теперь вносится изменение, чтобы добавить тему через environment объект в Title
.
final class Theme: ObservableObject { @Published var textColor: Color = .gray } struct Title: View { @EnvironmentObject var theme: Theme let title: String var body: some View { Text(title) .font(.largeTitle) .foregroundStyle(theme.textColor) } } #Preview { Title(title: "Title") .environmentObject(Theme()) // crashes without this! }
Это корректно для превью Title.swift
, но вызывает сбой в TitleSubtitle.swift
! Это легко исправить, если вы знаете превью, которые в конечном итоге используют Title
, но в большой кодовой базе это может быть в файле, который вы даже никогда не видели раньше.
Добавление тестов
Вместо того чтобы вручную тестировать каждое превью, вы можете написать юнит-тест, который будет автоматически запускать каждое превью в вашем приложении или фреймворке. Просто подключите пакет SnapshotPreviews к вашей тестовой цели и скопируйте следующий код:
import SnapshottingTests // This is an XCTestCase you can run in Xcode final class MyPreviewTest: PreviewLayoutTest { }
Тестовые функции будут автоматически добавлены для каждого предварительного просмотра в приложении. Вы можете легко просмотреть результаты в Xcode или автоматизировать их запуск в рамках CI с помощью Fastlane или xcodebuild.
Успешный результат будет выглядеть следующим образом:
И предыдущий пример приведут к аварийному завершению работы с указанием проблемы в стек-трейсе:
Как это работает
Это работает путем чтения метаданных типов Swift в бинарном файле приложения, чтобы найти все записи о соответствии протоколу PreviewProvider
. Соответствующие типы динамически инициализируются и вызываются для получения представления SwiftUI для каждого провайдера. Затем представления декомпозируется на превью с помощью VariadicView
.
Благодаря динамической природе среды выполнения Objective-C, для каждого обнаруженного превью в тестовый класс добавляется новая тестовая функция. Поскольку все это происходит автоматически, вам не нужно писать никакого кода, просто добавьте новые превью в ваше приложение, и оно будет протестировано!
Заключительные мысли
Написание тестов всегда кажется худшей частью тестирования, а это простой способ получить большое тестовое покрытие без необходимости писать какие-либо тесты! Как правило, модульные тесты не включают код, связанный с представлением, так что это также отличный способ увеличить процент покрытия кода, выходящий за рамки обычных модульных тестов. И наконец, хотя пример, который мы рассмотрели в этой заметке, не является ошибкой в производственном приложении, он все равно отнимал бы время разработчиков, если бы не был пойман с помощью тестов, потому что всем, кто использует затронутые превью, пришлось бы ждать исправления. Вылавливание таких проблем до слияния — отличный способ ускорить разработку с использованием превью!