SA0703 — Тестирование и отладка (Testing & Debugging)
Содержание страницы
Добавление тестов в приложение обеспечивает встроенный и автоматизированный способ гарантировать, что ваше приложение делает то, что вы от него ожидаете. И тесты не только проверяют, работает ли ваш код должным образом, но и гарантируют, что будущие изменения не нарушат существующую функциональность.
В этой главе вы узнаете, как реализовать тесты пользовательского интерфейса в приложении SwiftUI и на что следует обратить внимание при тестировании пользовательского интерфейса в соответствии с этой новой парадигмой.
Различные типы тестов
Существует три типа тестов, которые вы будете использовать в своих приложениях. В порядке возрастания сложности это юнит-тесты, интеграционные тесты и тесты пользовательского интерфейса.
Основой всех тестов и всех других тестов является модульный тест. Каждый модульный тест гарантирует, что вы получите ожидаемый результат, когда функция обрабатывает заданный ввод. Несколько модульных тестов могут тестировать один и тот же фрагмент кода, но каждый модульный тест должен фокусироваться только на одной единице кода. Выполнение модульного теста должно занимать миллисекунды. Вы будете часто бегать за ними, поэтому хотите, чтобы они бежали быстро.
Следующим тестом в иерархии тестирования является интеграционный тест. Интеграционные тесты проверяют, насколько хорошо различные части вашего кода работают друг с другом и насколько хорошо ваше приложение работает с внешним миром, например с внешними API. Интеграционные тесты более сложны, чем модульные тесты; их выполнение обычно занимает больше времени, и в результате вы будете запускать их реже.
Самый сложный тест-это тест пользовательского интерфейса, или тест пользовательскогоинтерфейса; эти тесты проверяют поведение вашего приложения, обращенное к пользователю. Они имитируют взаимодействие пользователя с приложением и проверяют, что пользовательский интерфейс ведет себя так, как ожидалось, после ответа на взаимодействие.
По мере продвижения вверх по иерархии тестирования каждый уровень тестирования проверяет более широкий спектр действий в приложении. Например, модульный тест проверяет, что calculateTotal()
метод в вашем приложении возвращает правильную сумму для заказа. Интеграционный тест проверит, правильно ли ваше приложение определяет наличие товаров в заказе на складе. Тест пользовательского интерфейса проверяет, что после добавления товара в заказ сумма, отображаемая пользователю, отображает правильное значение.
SwiftUI-это новый визуальный фреймворк, поэтому в этой главе речь пойдет о том, как писать тесты пользовательского интерфейса для приложений SwiftUI. Вы также узнаете, как отлаживать приложение SwiftUI и тесты, добавляя тесты пользовательского интерфейса в простое приложение калькулятора.
Отладка приложений SwiftUI
Начните с открытия стартового проекта для этой главы, создайте и запустите приложение; это простой калькулятор. Приложение также поддерживает Catalyst, поэтому оно работает на iOS, iPadOS и Mac. Выполните несколько вычислений с помощью калькулятора, чтобы получить представление о том, как он работает.
Отладка SwiftUI требует немного большей предусмотрительности и планирования, чем большинство тестов, потому что пользовательский интерфейс и код смешиваются в парадигме SwiftUI. Поскольку представления SwiftUI-это не что иное, как код, они выполняются так же, как и любой другой код.
Откройте SwiftCalcView.swift и найдите следующие строки кода. Они должны быть рядом с линией 138:
Button(action: { if let val = Double(display) { memory += val display = "" pendingOperation = .none } else { // Add Bug Fix Here display = "Error" } }, label: { Text("M+") }) .buttonStyle(CalcButtonStyle())
Этот код определяет кнопку для пользовательского интерфейса. Первый блок определяет действие, которое необходимо выполнить, когда пользователь нажимает на кнопку. Следующий блок определяет, как выглядит кнопка в представлении. Даже если эти два фрагмента кода смежны, они не всегда будут выполняться одновременно.
Установка точек останова
Чтобы остановить код во время выполнения приложения, вы устанавливаете точку останова, чтобы сообщить отладчику остановить выполнение кода, когда он достигнет определенной строки кода. Затем вы можете проверять переменные, пошагово просматривать код и исследовать другие элементы в вашем коде.
Чтобы установить точку останова, наведите курсор на соответствующую строку и нажмите *Command + * или выберите *в меню Debug ▸ Точки останова ▸ Добавить точку останова в текущую строку*. Вы также можете нажать на поле в строке, где вы хотите установить точку останова.
Используйте один из этих методов, чтобы установить две точки останова: одну на кнопке, а затем одну на первой строке кода action:
кнопки M+, как показано ниже:

Точки останова приложения
Примечание: До Xcode 13 вы также могли запускать предварительный просмотр в режиме отладки. Apple удалила эту функцию в Xcode 13, и теперь вы должны использовать симулятор или устройство для отладки представлений вашего приложения. Точки останова будут проигнорированы предварительным просмотром.
Запустите свое приложение. Через мгновение приложение достигает точки останова на элементе Text
управления кнопкой. Когда он достигает точки останова Text()
, выполнение приостанавливается так же, как и в случае с любым другим кодом.
Когда выполнение достигает точки останова, приложение приостанавливается, и Xcode возвращает вам управление. В нижней части окна Xcode вы увидите область отладки, состоящую из двух окон под редактором кода. Если вы не видите область отладки, перейдите к просмотру ▸ Область отладки ▸ Показать область отладки или нажмите Shift + Command + Y, чтобы переключить область отладки.
Левая панель области отладки содержит представление переменных. Он показывает вам текущее состояние и значение активных переменных в вашем приложении. Правая панель содержит интерактивную консоль, самый сложный и мощный инструмент для отладки в Xcode.

Переменные Консоли
Использование точек останова не просто останавливает код; он также может сказать вам, действительно ли выполнение приложения достигло этого фрагмента кода. Если точка останова не срабатывает, значит, вы знаете, что что-то заставило приложение пропустить код.
Смешивание кода и элементов пользовательского интерфейса в SwiftUI может привести к путанице, но точки останова могут помочь вам понять, что и когда выполняется. Если вы добавляете точку останова и она никогда не прерывается, то вы знаете, что выполнение никогда не достигало объявления и интерфейс не будет содержать элемент. Если ваша точка останова все-таки будет поражена, вы можете проверить состояние приложения в этой точке.
Изучение контроля точек останова
При остановке в точке останова вы увидите панель инструментов между редактором кода и областью отладки. Первая кнопка на этой панели инструментов переключает видимость области отладки. Вторая кнопка отключает все точки останова, но не удаляет их. Третья кнопка продолжает выполнение приложения. Вы также можете выбрать Debug ▸ Continue в меню, чтобы продолжить выполнение приложения.
Следующие три кнопки позволяют вам пошагово просматривать код. Щелчок по первой выполняет текущую строку кода, включая любые вызовы методов или функций. Вторая кнопка также выполняет текущую строку кода, но если есть вызов метода, он приостанавливается в первой строке кода внутри этого метода или функции. Кнопка final выполняет код до конца текущего метода или функции.

Панель отладки
Продолжайте выполнение приложения с помощью кнопки панели инструментов или меню. После еще одной короткой паузы вы увидите, как появится вид.
Нажмите кнопку M+ на экране предварительного просмотра, чтобы увидеть триггер точки останова. Когда это происходит, код останавливается в точке останова в первой строке блока Button
действий.
В командной строке (lldb) консоли выполните следующие действия:
po _memory
po
Команда в консоли позволяет проверить состояние объекта. Обратите внимание на подчеркивание в начале имени переменной. На данный момент просто знайте, что в представлении SwiftUI вам нужно будет префикс имени переменной с подчеркиванием. Вы увидите, что результат показывает содержимое переменной memory
состояния:

Вывод отладчика
Добавление тестов пользовательского интерфейса
В этом коде есть ошибка, которую вы заметите, когда продолжите. Значение по умолчанию на дисплее-пустая строка, и дисплей преобразует пустую строку в 0. Однако код кнопки M+ пытается преобразовать пустую строку в двойную. Когда это преобразование завершается неудачно, пользователю отображается ошибка значения.
Даже если вы не пишете тест для каждого случая в своем приложении, полезно создавать тесты, когда вы обнаруживаете ошибки. Создание теста гарантирует, что вы действительно исправили ошибку. Он также обеспечивает раннее уведомление, если эта ошибка появится в будущем. В следующем разделе вы напишете тест пользовательского интерфейса для этой ошибки.
Примечание: Удалите только что созданные точки останова. Вы можете сделать это, щелкнув правой кнопкой мыши точку останова и выбрав пункт Удалить точку останова. Вы можете отключить точки останова, нажав на них. Они должны стать светло-голубыми. Нажимайте на них снова всякий раз, когда захотите их снова активировать.
В стартовом проекте перейдите к файлу ▸ New ▸ Target.… Выберите iOS и прокрутите вниз, чтобы найти раздел тестирования. Выберите Пакет тестирования пользовательского интерфейса и нажмите кнопку Далее.
Xcode предлагает имя пакета тестов, которое сочетает в себе имя проекта и тип теста. Примите предложение SwiftCalcUITests. Выберите SwiftCalc в качестве проекта и цели для тестирования. Наконец, нажмите кнопку Готово.
В навигаторе проекта вы увидите новую группу с именем SwiftCalcUITests. Эта новая цель содержит фреймворк, в котором вы создаете свои тесты пользовательского интерфейса; разверните группу и откройте SwiftCalcUITests.swift.
Вы увидите, что файл начинается с импорта XCTest
. XCTest
Фреймворк содержит стандартные библиотеки тестирования Apple. Вы также увидите XCTestCase
, от чего наследуется тестовый класс, от которого все тестовые классы наследуют свое поведение.
Вы также увидите четыре метода по умолчанию, предоставленные в шаблоне Xcode. Первые два метода являются важной частью вашего тестового процесса. Тестовый процесс вызывает setUpWithError()
перед каждым тестовым методом в классе, а затем вызывает tearDownWithError()
после завершения каждого тестового метода.
Помните: тест должен проверить, что известный набор входных данных приводит к ожидаемому набору выходных данных. Вы setUpWithError()
должны убедиться, что ваше приложение находится в этом известном состоянии перед началом каждого метода тестирования. Вы используете tearDownWithError()
очистку после каждого теста, чтобы вернуться к известному стартовому состоянию для следующего теста.
Обратите внимание на следующую строку вsetUpWithError()
:
continueAfterFailure = false
Эта линия прекращает тестирование при возникновении сбоя. Установка этого значения false
останавливает процесс тестирования после первого сбоя. Учитывая характер тестирования пользовательского интерфейса, вы почти всегда окажетесь в неизвестном состоянии, когда тест завершится неудачей. Вместо того чтобы продолжать то, что часто является длительными тестами для очень маленькой и потенциально неверной информации, вы должны остановиться и исправить проблему сейчас.
В этой главе вам не придется выполнять никаких других работ по настройке или очистке тестов.
Третий метод в шаблонеtestExample()
-это метод , который содержит образец теста. Вы также увидите, что рядом с именем метода есть маленький серый ромб; это означает, что Xcode распознает его как тест, но тест еще не запущен. После запуска теста ромб изменится на зеленую галочку, если тест пройдет успешно, или на белый крестик на красном фоне после завершения, если тест пройдет неудачно.
Имена тестов должны начинаться с test. Если нет, то платформа тестирования игнорирует метод и не будет выполнять его при тестировании. Например, фреймворк игнорирует названный метод myCoolTest()
, но он будет выполнен testMyCoolCode()
.

Испытательные образцы
В образце теста вы увидите комментарий, предлагающий “Использовать запись, чтобы начать писать тесты пользовательского интерфейса”. Запись может сэкономить время при создании тестов пользовательского интерфейса, но здесь вы будете писать эти тесты с нуля.
Создание теста пользовательского интерфейса
Правильные имена тестов должны быть точными и ясными в отношении того, что тест проверяет, поскольку приложение может получить большое количество тестов. Четкие имена позволяют легко понять, что не удалось. В названии теста должно быть указано, что он тестирует, обстоятельства теста и каким должен быть результат.
Переименовать testExample()
в testPressMemoryPlusAtAppStartShowZeroInDisplay()
. Кажется ли это действительно долгим? Имена тестов-это не место и не время для краткости; название должно четко представлять все три элемента с первого взгляда.
Тест пользовательского интерфейса начинается с приложения в состоянии “только что запущен”, поэтому вы можете писать каждый тест так, как будто приложение только что запустилось. Обратите внимание, что это не означает, что состояние приложения сбрасывается при каждом запуске. Вы используете методы setUpWithError()
иtearDownWithError()
, чтобы убедиться, что ваше приложение находится в определенном известном состоянии перед каждым тестом, и очистить все изменения, внесенные во время теста. Если вы ожидаете, что во время выполнения теста будут присутствовать параметры, данные, конфигурация, местоположение или другая информация, то их необходимо настроить.
Очистите комментарии после app.launch()
команды и добавьте точку останова в app.launch()
строке теста.
Существует несколько способов запуска тестов пользовательского интерфейса. Во-первых, вы можете перейти к навигатору тестов, нажав Command + 6 в Xcode. Вы увидите свой тест вместе с testLaunchPerformance()
тестом по умолчанию. Если вы наведете курсор мыши на название теста, то увидите серую кнопку воспроизведения. Наведите курсор мыши на серый ромб слева от имени функции, и вы увидите кнопку Воспроизведения.
Если вы наведете курсор на имя класса или платформы тестирования либо в Навигаторе тестов, либо в исходном коде, появится аналогичная кнопка воспроизведения, которая запустит последовательный запуск группы тестов.
Этот тест не является полным, так как он ничего не проверяет. Это хорошее время, чтобы запустить его и немного узнать о том, как проходит тест. А пока используйте любой из этих методов для начала testPressMemoryPlusAtAppStartShowZeroInDisplay()
теста.
Тесты-это быстрый код, поэтому вы можете отлаживать тесты так же, как отлаживаете свое приложение! Иногда вам нужно будет определить, почему тест ведет себя не так, как ожидалось. Когда тест достигнет точки останова, вы увидите остановку выполнения, точно так же, как ваша точка останова вела бы себя в любом другом коде.
Основной элемент, который вы хотите исследовать, — это app
элемент, в котором вы разместили точку останова. Перейдите по команде, чтобы запустить приложение с помощью кнопки панели инструментов, нажав F6 или выбрав Debug ▸ Шаг в меню. В симуляторе вы увидите запуск приложения. Как только в консоли появится приглашение (lldb), введите po app
его .
Вы увидите результат, аналогичный следующему:

команда приложения po
Вы исследуете app
объект, который вы объявили как XCUIApplication
подкласс XCUIElement
. Вы будете работать с этим объектом во всех ваших тестах пользовательского интерфейса.
app
Объект содержит дерево, которое начинается с приложения и продолжается через все элементы пользовательского интерфейса вашего приложения. Каждый из этих элементов также имеет свой тип XCUIElement
. Вы получите доступ к элементам пользовательского интерфейса в своем приложении, выполнив запросы фильтра по app
объекту, чтобы выбрать элементы в дереве, которое вы видите.
Далее вы увидите, как запустить запрос для поиска кнопок в приложении.
Доступ к элементам пользовательского интерфейса
Добавьте следующий код в конец метода тестирования:
let memoryButton = app.buttons["M+"] memoryButton.tap()
XCUIApplication
содержит набор элементов для каждого типа объекта пользовательского интерфейса. Этот запрос сначала фильтруется только .buttons
в приложении. Затем он фильтруется к элементу, который имеет метку M+.
Приложения SwiftUI рендерятся с собственными элементами платформы; они не являются новыми компонентами. Несмотря на то, что SwiftUI предоставляет новый способ определения интерфейса, он по-прежнему использует существующие элементы платформы. SwiftUI Button
становится a UIButton
на iOS и a NSButton
на macOS. В этом приложении фильтр соответствует метке, которую вы видели на выходе po app
.
Button, 0x600002498540, {{184.5, 102.5}, {45.0, 45.0}}, label: ’M+’
Как только у вас есть объект button, вы вызываете tap()
его. Этот метод имитирует, как кто-то нажимает на кнопку. Удалите точку останова при запуске приложения и повторите тест.

Первый тестовый запуск
Вы увидите, как приложение запускается и запускается в симуляторе по мере выполнения теста. Если вы посмотрите симулятор, то увидите, что на дисплее калькулятора отображается ошибка точно так же, как и при запуске вручную. Как только тесты будут выполнены, приложение остановится. Вы увидите, что серый ромб превращается в зеленую галочку как рядом с функцией, так и в Навигаторе тестов.
Зеленая галочка означает пройденный тест. В данном случае тест ничего не проверил. Фреймворк рассматривает тест, который не провалился, как проходной тест.
В тесте пользовательского интерфейса известный набор входных данных для вашего теста-это набор взаимодействий с приложением. Здесь вы выполнили взаимодействие, нажав кнопку M+, так что теперь вам нужно проверить результат. В следующем разделе вы увидите, как получить значение из элемента управления.
Чтение пользовательского интерфейса
Вы нашли кнопку M+, сопоставив метку кнопки. Однако это не сработает для отображения, потому что текст в элементе управления изменяется в зависимости от состояния приложения. Однако вы можете добавить атрибут к элементам интерфейса, чтобы его было легче найти в тесте. Откройте DisplayView.swift. В представлении найдите два комментария // Add display identifier
и замените их следующей строкой:
.accessibility(identifier: "display")
Этот метод устанавливает значение accessibilityIdentifer
для результирующего элемента пользовательского интерфейса. Несмотря на название, VoiceOver не читает accessibilityIdentifer
атрибут; это просто дает возможность дать элементу пользовательского интерфейса постоянную метку для тестирования. Если вы не предоставите этот идентификатор для элемента, он, как правило, будет таким же, как label
и для элемента управления, как и для кнопки M+.
Вернитесь к SwiftCalcUITests.swift. Добавьте следующий код в концеtestPressMemoryPlusAtAppStartShowZeroInDisplay()
:
// 1 let display = app.staticTexts["display"] // 2 let displayText = display.label // 3 XCTAssert(displayText == "0")
Вы написали свой первый настоящий тест! Вот что делает каждый шаг:
- Вы используете
accessibility(identifier:)
добавленный элемент для поиска элемента отображения в вашем приложении. - Результатом шага 1 является an
XCUIElement
, как и большинство элементов пользовательского интерфейса в тесте пользовательского интерфейса. Вы хотите исследоватьlabel
свойство элемента, содержащего текст метки. - Утверждение используется для проверки соответствия метки ожидаемому результату. Все тестовые утверждения начинаются с префикса
XCT
— пережитка соглашений об именах Objective-C. В каждом тесте вы выполняете одно или несколько утверждений, которые определяют, пройден тест или нет.
В этом случае вы проверяете, что текст для отображения-это строка “0″. Вы уже знаете, что результатом будет неудачный тест, но все же запустите завершенный тест, чтобы посмотреть, что произойдет. Вы получите ожидаемый сбой и увидите белый крестик на красном.

Провалил первый тест
Теперь, когда у вас есть тест, вы можете исправить ошибку!
Исправление ошибки
Откройте SwiftCalcView.swift, найдите комментарий в действии для кнопки M+, которая читает// Add Bug Fix Here
, и измените следующую строку на чтение:
display = ""
Повторите тест. Вы увидите, что это пройдет.

Первое прохождение теста
Возможно, вам интересно, почему вы приложили дополнительные усилия: вы изменили одну строку кода, чтобы исправить ошибку, но добавили в свое приложение еще один фреймворк, и вам пришлось написать пять строк кода для создания теста.
Хотя это может показаться большой работой, чтобы доказать, что вы исправили крошечную проблему, вы найдете этот шаблон написания неудачного теста, исправления ошибки и последующей проверки того, что тест пройден, полезным шаблоном. Взяв существующее приложение без тестов и добавляя тест каждый раз, когда вы исправляете ошибку, вы быстро создаете полезный набор тестов на данный момент и, что более важно, на будущее.
Добавление более сложных тестов
В идеале вы будете создавать свои тесты пользовательского интерфейса одновременно с созданием своего пользовательского интерфейса. Таким образом, по мере того как ваш пользовательский интерфейс будет становиться все более конкретизированным, ваш набор тестов будет расширяться вместе с ним. Однако в реалиях современной разработки вы обычно добавляете тесты после того, как приложение уже существует.
Добавьте более сложный тест, который проверяет, что сложение двух однозначных чисел дает правильную сумму. Откройте SwiftCalcUITests.swift и добавьте следующий тест в конце класса:
`func testAddingTwoDigits() {
let app = XCUIApplication()
app.launch()
let threeButton = app.buttons[«3»]
threeButton.tap()
let addButton = app.buttons[«+»]
addButton.tap()
let fiveButton = app.buttons[«5»]
fiveButton.tap()
let equalButton = app.buttons[«=»]
equalButton.tap()
let display = app.staticTexts[«display»]
let displayText = display.label
XCTAssert(displayText == «8»)
}`
Когда вы запускаете тест, вы можете не ожидать, что он провалится. Три плюс пять равно восьми, верно? Потратьте минутку, чтобы посмотреть, сможете ли вы понять, почему, прежде чем продолжить.
Ваш тест сравнивает метку дисплея со строкой 8. Поместите точку останова в XCTAssert
оператор at и повторите тест. Подождите, пока выполнение не остановится в точке останова. В командной строке консоли введите po displayText
.
Вы увидите, что текст на дисплее читается 8.0, а не 8. Тест пользовательского интерфейса фокусируется на пользовательском интерфейсе, а не на закулисных элементах. Модульный тест, напротив, проверил бы, что код правильно рассчитал 3 + 5 = 8. Тест пользовательского интерфейса должен проверить, что видит пользователь при выполнении этого вычисления.
Измените конечную строку теста на:
XCTAssert(displayText == "8.0")
Повторите тест, и вы увидите, что он прошел успешно.

Прохождение теста
XCTAssert()
оценивает условие и терпит неудачу, если оно неверно. Если бы вы использовали более конкретное XCTAssertEqual(displayText, "8")
первоначальное утверждение, оно предоставило бы информацию, обнаруженную с помощью отладчика в сообщении об ошибке. Раньше вы XCTAssert()
изучали отладку неудачного теста. Измените свой тест на XCTAssertEqual(displayText, "8.0")
и убедитесь, что он все еще проходит.
Затем вы внесете изменения в пользовательский интерфейс и, поскольку хотите сформировать хорошие привычки тестирования, добавите тест для проверки этих изменений.
Моделирование взаимодействия с пользователем
Сначала вы добавите жест, чтобы при перемещении дисплея памяти влево он был очищен. Эффект жеста работает так же, как при нажатии клавиши MC, устанавливая значение memory
равным нулю.
Откройте MemoryView.swift. В верхней части body
определения, прямо перед HStack
этим , добавьте жест:
let memorySwipe = DragGesture(minimumDistance: 20) .onEnded { _ in memory = 0.0 }
Вы можете добавить этот жест на дисплей памяти. Найдите текст // Add gesture here
и замените его на:
.gesture(memorySwipe)
Как и в случае с основным дисплеем, вы также добавите идентификатор на дисплей памяти. Добавьте следующую строку нижеText("\(memory)")
:
.accessibility(identifier: "memoryDisplay")
Создайте и запустите приложение; введите несколько цифр и нажмите M+, чтобы сохранить значение в памяти. Появится дисплей памяти, на котором будут показаны сохраненные цифры. Проведите пальцем по дисплею памяти влево и убедитесь, что он очистился.

Приложение Swipe
Теперь, поскольку вы практикуете хорошие привычки разработки и тестирования, вы добавите тест пользовательского интерфейса, чтобы проверить это поведение. Шаги теста повторяют действия, которые вы только что выполнили вручную.
Откройте SwiftCalcUITests.swift и добавьте следующий код после существующих тестов:
`func testSwipeToClearMemory() {
let app = XCUIApplication()
app.launch()
let threeButton = app.buttons[«3»]
threeButton.tap()
let fiveButton = app.buttons[«5»]
fiveButton.tap()
let memoryButton = app.buttons[«M+»]
memoryButton.tap()
let memoryDisplay = app.staticTexts[«memoryDisplay»]
// 1
XCTAssert(memoryDisplay.exists)
// 2
memoryDisplay.swipeLeft()
// 3
XCTAssertFalse(memoryDisplay.exists)
}`
Вы уже видели большую часть этого кода раньше. Вот что делает новый код:
exists
Свойство anXCUIElement
-этоtrue
когда элемент существует. Если бы дисплей памяти не был виден, то это утверждение потерпело бы неудачу.swipeLeft()
Метод производит действие свайпа влево на вызывающем элементе. Существуют дополнительные методы дляswipeRight()
,swipeUp()
иswipeDown()
.XCTAssertFalse()
Тест действует как противоположность forXCTAssert
. Он завершается успешно, когда проверяемое значениеfalse
вместоtrue
. Свайп должен быть равенmemory
нулю после жеста, а действие должно скрыть дисплей памяти, уничтожив его существование.
Запустите тест, и вы увидите, что он подтверждает, что ваш пользовательский интерфейс работает должным образом.
Существует много элементов тестирования, помимо тех, которые обсуждаются в этой главе. Вот некоторые из распространенных атрибутов и методов, которые вы не имели возможности использовать в этой главе::
- .isHittable: элемент является hittable, если он существует и пользователь может щелкнуть, коснуться или нажать на него в его текущем местоположении. Закадровый элемент существует, но не может быть поражен.
- .TypeText(): Этот метод действует так, как будто пользователь вводит текст в вызывающий элемент управления.
- .press(forDuration:): Это позволяет выполнять касание одним пальцем в течение заданного промежутка времени.
- .press(forDuration:thenDragTo:): Эти
swipe
методы не дают никакой гарантии скорости жеста. Вы можете использовать этот метод для выполнения более точного перетаскивания. - .waitForExistence(): полезно приостанавливать, когда элемент может появиться на экране не сразу.
Полный список методов и свойств вы найдете в документации Apple по адресу https://developer.apple.com/documentation/xctest/xcuielement
Тестирование нескольких платформ
Большая часть обещаний SwiftUI связана с созданием приложений, работающих на нескольких платформах Apple. Ваше приложение iOS может стать приложением macOS с очень небольшим количеством работы: примерный проект для этой главы поддерживает Catalyst, позволяя приложению работать на macOS. Однако всегда есть несколько вещей, о которых вам придется позаботиться самому, чтобы ваши приложения и их тесты работали должным образом на всех платформах.
В Xcode измените целевое устройство для приложения на Мой Mac. В настройках проекта выберите цель SwiftCalc. Выберите Подпись и возможности, задайте команду и убедитесь, что сертификат подписи настроен на подпись для локального запуска. Теперь создайте и запустите приложение, чтобы оно работало для macOS.
Об использовании SwiftUI с различными операционными системами вы узнаете в главе 21 “Создание приложения для Mac”. Поскольку, как вы ожидаете, работа на разных платформах может потребовать изменений пользовательского интерфейса, тестирование пользовательского интерфейса в различных операционных системах потребует разных тестов. Некоторые действия пользовательского интерфейса транслируются напрямую; например, нажатие кнопки на устройстве iOS работает точно так же, как нажатие мыши на кнопку в macOS.
Если целевое устройство все еще настроено на мой Mac, создайте и запустите свои тесты. Вы получите ошибку компиляции: “Значение типа ’XCUIElement’ не имеет члена ’swipeLeft’”. Ага — не все действия имеют прямые эквиваленты в каждой операционной системе. Это .swipeLeft()
действие приводит к ошибке, поскольку Catalyst не предоставляет эквивалента салфетки для macOS в тестовой среде.
Решение лежит в блоках условной компиляции Xcode. Эти блоки говорят Xcode компилировать обернутый код только тогда, когда одно или несколько условий являются истинными во время компиляции. Блок начинается с #if
того, что за ним следует тест. Вы можете опционально использовать #elseif
и #else
как с традиционными if
операторами, и вы заканчиваете блок #endif
.
Вы хотите исключить неудачный тест при тестировании приложения в Catalyst. Оберните testSwipeToClearMemory()
тест внутри targetEnvironment
проверки, чтобы исключить тесты из Catalyst:
`#if !targetEnvironment(macCatalyst)
// Test to exclude
endif`
Вы также можете указать операционную систему в качестве условия. Операционная система может быть любой из macOS
, iOS
, watchOS
, tvOS
или Linux
. Например, XCTest еще не поддерживает watchOS. Если вы создаете приложение для watchOS, вам нужно будет обернуть тесты, чтобы предотвратить запуск кода против watchOS. Чтобы исключить тесты из watchOS, оберните тесты аналогичной проверкой, исключающей watchOS:
`#if !os(watchOS)
// Your XCTest code
endif`
При разработке тестов пользовательского интерфейса для кросс-платформенных приложений рекомендуется объединять тесты для конкретных операционных систем в один тестовый класс. Используйте условные оболочки компиляции, чтобы изолировать код для компиляции только под целевой платформой и операционной системой.
Отладка представлений и изменений состояний
При отладке приложения SwiftUI вы часто сталкиваетесь с ситуациями, когда производительность снижается из-за того, что представление перерисовывается чаще, чем ожидалось. Отследить, почему SwiftUI перерисовывает представление, можно с помощью нескольких трюков. Вы можете использовать метод Питера Стейнбергера для определения того, когда вид перерисовывается, который назначает ему случайный цвет фона. Откройте DisplayView.swift и добавьте следующий код после инструкции import:
extension Color { // Return a random color static var random: Color { return Color( red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1) ) } }
Этот код создает расширение для Color
названного типа random
. При каждом вызове он будет представлять новый случайный цвет. Когда вы применяете его к виду, каждый раз, когда он перерисовывается, он получает новый цвет фона, что облегчает визуальную идентификацию перерисованных видов.
Чтобы использовать расширение, примените его к виду так же, как и к любому другому цвету. В конце HStack
представления добавьте следующий код:
.background(Color.random)
Запустите приложение и нажмите несколько цифровых кнопок, чтобы изменить отображение. Вы увидите, что цвет фона DisplayView
меняется на случайный цвет каждый раз, когда вы нажимаете кнопку.

Случайный цвет фона при изменении вида
Хотя это может показать вам, какое представление меняется, оно не говорит вам, почему. SwiftUI 3 представил новый метод, который поможет решить эту проблему. Вы можете использовать новый Self._printChanges()
метод, чтобы определить, что вызвало перерисовку представления. Откройте DisplayView.swift и добавьте следующий код после var body: some View {
объявления body
свойства для представления:
let _ = Self._printChanges()
Этот странный код говорит SwiftUI определить изменение, которое привело к тому, что SwiftUI решил перерисовать представление. SwiftUI отобразит имя представления и свойства, которые изменяются каждый раз, когда оно рисуется. Обратите внимание, что метод начинается с подчеркивания, намекающего на то, что вы должны использовать его только при отладке и удалять из готового приложения. Вы должны поместить его в body
свойство представления, которое хотите отслеживать.
Запустите приложение и снова наберите несколько кнопок. Вы по-прежнему будете видеть, как цвет фона дисплея меняется при каждом нажатии. В интерактивной консоли вы увидите сообщение с указанием вида, который изменился, а также свойства или свойств, которые привели к перерисовке вида при каждом нажатии.

Отображение свойств, вызывающих перерисовку представления
Не забудьте вынуть их перед отправкой приложения.
Вызов
Задача: Добавить жест салфетки
Как отмечалось ранее, жест салфетки для очистки памяти не работает в Catalyst. В приложении вам нужно будет предоставить альтернативный метод получения того же результата.
Для версии Catalyst этого приложения добавьте жест двойного нажатия на дисплей памяти, чтобы получить тот же результат, что и жест салфетки. ОбновитеtestSwipeToClearMemory()
, чтобы проверить функциональность соответствующим образом в каждой среде.
Решение проблемы
Вы должны начать с добавления нового жеста двойного касания. Измените текущее определение жеста в MemoryView.swift на:
`#if targetEnvironment(macCatalyst)
let doubleTap = TapGesture(count: 2)
.onEnded { _ in
self.memory = 0.0
}
else
let memorySwipe = DragGesture(minimumDistance: 20)
.onEnded { _ in
self.memory = 0.0
}
endif`
Это сохраняет текущий жест салфетки на телефонах и планшетах, но создает жест касания, который ожидает двух нажатий на Catalyst.
Теперь обновите дисплей памяти, чтобы точно так же использовать правильный жест для каждой среды.
`#if targetEnvironment(macCatalyst)
Text(«(memory)»)
.accessibility(identifier: «memoryDisplay»)
.padding(.horizontal, 5)
.frame(
width: geometry.size.width * 0.85,
alignment: .trailing
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(lineWidth: 2)
.foregroundColor(Color.gray)
)
// Add gesture here
.gesture(doubleTap)
else
Text(«(memory)»)
.accessibility(identifier: «memoryDisplay»)
.padding(.horizontal, 5)
.frame(
width: geometry.size.width * 0.85,
alignment: .trailing
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(lineWidth: 2)
.foregroundColor(Color.gray)
)
// Add gesture here
.gesture(memorySwipe)
endif`
SwiftUI не поддерживает установку targetEnvironment()
условия внутри модификаторов для представления. Это означает, что вы должны разместить вид дважды, меняя нужный жест в каждом из них. Обычно вы извлекаете представление в подвид, чтобы уменьшить объем кода.
Наконец, обновите свой testSwipeToClearMemory()
тест и замените код после второго шага ранее на:
`#if targetEnvironment(macCatalyst)
memoryDisplay.doubleTap()
else
memoryDisplay.swipeLeft()
endif`
Это вызовет соответствующий жест пользовательского интерфейса в каждой среде. Запустите тест как на моем Mac, так и на симуляторе iOS, чтобы проверить внесенные изменения.
Ключевые моменты
- Построение и отладка тестов требуют немного большего внимания из-за сочетания кода и элементов пользовательского интерфейса в SwiftUI.
- Вы можете использовать точки останова и отладку в SwiftUI так же, как и в стандартном Swift-коде.
- Тесты автоматизируют проверку поведения вашего кода. Тест должен гарантировать, что при известном входе и известном начальном состоянии произойдет ожидаемый выход.
- Тесты пользовательского интерфейса или пользовательского интерфейса проверяют, что взаимодействие с интерфейсом вашего приложения дает ожидаемые результаты.
- Добавьте an
accessibilityIdentifer
к элементам, которые не имеют статического текста для своей метки, чтобы улучшить расположение для тестирования. - Вы найдете все элементы пользовательского интерфейса из
XCUIApplication
элемента, используемого для запуска приложения в тесте. - Методы и свойства позволяют находить пользовательский интерфейс в тестах и взаимодействовать с ним так, как это сделал бы пользователь.
- Разные платформы часто нуждаются в разных тестах пользовательского интерфейса. Используйте условную компиляцию для сопоставления тестов с платформой и операционной системой.
- Вы можете использовать
Self._printChanges()
для просмотра изменения состояния, которое вызывает перерисовку представления.
Куда идти дальше?
Эта глава содержит введение в тестирование и отладку проектов SwiftUI. Вашей отправной точкой для более глубокого изучения должна стать документация Apple по XCTest по адресу
Вы также найдете больше о тестировании в видео-тестировании WWDC 2019 в Xcode по адресу
Apple часто выпускает новые видеоролики об изменениях, связанных с отладкой, каждый год на WWDC. В 2019 году есть видео, посвященное отладке в Xcode 11 по адресу
На 2021 год есть видео об улучшениях точек останова по адресу
Как только вы будете готовы углубиться в отладку, вы также захотите посмотреть LLDB: Beyond “po” по адресу