XCUITest: Как перейти в код приложения? Как изменить состояние тестируемого приложения? - PullRequest
3 голосов
/ 24 января 2020

Исходя из Android / Espresso, я все еще борюсь с XCUITest и тестированием пользовательского интерфейса для iOS. Мой вопрос касается двух связанных, но различных проблем:

  1. Как скомпилировать и связать с источниками тестируемого приложения?
  2. Как перейти к методам тестируемого приложения и изменить его состояние во время выполнения?

Чтобы решить эти вопросы, мы должны сначала понять различия между «целями модульного тестирования» XCode и «целями тестирования пользовательского интерфейса».

  • XCUITests запускаются внутри совершенно отдельного процесса и не могут переходить к методам тестируемого приложения. Более того, по умолчанию XCUITests не связаны с какими-либо источниками тестируемого приложения.

  • Напротив, модульные тесты XCode связаны с источниками приложения. Также есть опция «@testable import». Теоретически это означает, что модульные тесты могут перейти в произвольный код приложения. Тем не менее, модульные тесты не работают против фактического приложения. Вместо этого модульные тесты запускаются для урезанной версии iOS SDK без какого-либо пользовательского интерфейса.

Теперь существуют различные обходные пути для этих ограничений:

  • Добавьте некоторые выбранные исходные файлы к цели тестирования пользовательского интерфейса. Это не позволяет вызывать приложение, но, по крайней мере, позволяет обмениваться выбранным кодом между тестами приложения и пользовательского интерфейса.

  • Передать аргументы запуска через CommandLine.arguments из тестов пользовательского интерфейса в тестируемое приложение. Это позволяет применять специфичные для теста конфигурации c к тестируемому приложению. Однако эти аргументы запуска должны анализироваться и интерпретироваться приложением, что приводит к загрязнению приложения тестовым кодом. Более того, аргументы запуска - это неинтерактивный способ изменить поведение тестируемого приложения.

  • Реализация «отладочного интерфейса», доступного только для XCUITest. Опять же, у этого есть недостаток загрязнения кода приложения.

Это приводит к моим заключительным вопросам:

  • Какие существуют альтернативные методы для выполнения тестов XCUI более мощный / динамический / гибкий?

  • Могу ли я скомпилировать и связать тесты пользовательского интерфейса со всем исходным приложением и всеми зависимостями модуля, а не только с несколькими выбранными файлами?

  • Можно ли получить мощность инструментальных тестов Android + эспрессо, где мы можем выполнять произвольные изменения состояния в тестируемом приложении?

Почему Нам нужно это

В ответ на @theMikeSwan я хотел бы прояснить свою позицию по архитектуре тестирования пользовательского интерфейса.

Тесты пользовательского интерфейса не должны ссылаться на код приложения, они предназначены чтобы симулировать пользователя, нажав внутри вашего приложения. Если бы во время этих тестов вы запрыгнули в код приложения, вы бы больше не тестировали то, что делает ваше приложение в реальном мире, вы бы тестировали то, что оно делает, когда им манипулировали так, как никогда не мог сделать пользователь. Тесты пользовательского интерфейса не должны нуждаться в каком-либо коде приложения больше, чем пользователь.

Я согласен, что манипулирование приложением таким способом является анти-паттерном, который должен использоваться только в редких ситуациях , Тем не менее, у меня совсем другая позиция относительно того, что должно быть возможно. На мой взгляд, правильный подход к тестированию пользовательского интерфейса - это не тестирование черного ящика, а тестирование серого ящика. Хотя мы хотим, чтобы тесты пользовательского интерфейса были как можно более «черными», в некоторых ситуациях мы хотим углубиться в детали реализации тестируемого приложения. Просто приведу несколько примеров:

  • Расширяемость: Ни одна инфраструктура тестирования пользовательского интерфейса не может предоставить API для каждого варианта использования. Требования к проекту различны, и бывают случаи, когда мы хотим написать собственную функцию для изменения состояния приложения.

  • Утверждения внутреннего состояния: Я хочу иметь возможность написать пользовательские утверждения для состояния приложения (утверждения, которые зависят не только от пользовательского интерфейса). В моем текущем Android проекте у нас была заведомо сломанная подсистема. Я установил эту подсистему с помощью пользовательских методов для защиты от ошибок регрессии.

  • Общие фиктивные объекты: В моем текущем проекте Android у нас есть нестандартное оборудование, которое не является доступны для тестов пользовательского интерфейса. Мы заменили это оборудование на фиктивные объекты. Мы запускаем утверждения для этих фиктивных объектов прямо из тестов пользовательского интерфейса. Эти утверждения бесперебойно работают через общую память. Более того, я не хочу загрязнять код приложения всеми ложными реализациями.

  • Хранить тестовые данные вне: В моем текущем проекте Android мы загружаем тестовые данные из JUnit прямо в приложение. С аргументами командной строки XCUITest это было бы гораздо более ограниченным.

  • Пользовательские механизмы синхронизации: В моем текущем проекте Android у нас есть классы-обертки вокруг инфраструктуры многопоточности синхронизировать наши тесты пользовательского интерфейса с фоновыми задачами. Эту синхронизацию трудно реализовать без разделяемой памяти (например, Espresso IdlingResources).

  • Простой обмен кодами: В моем текущем проекте iOS у меня простое определение файл для вышеупомянутых аргументов запуска. Это позволяет передавать аргументы запуска безопасным способом без дублирования строковых литералов. Хотя это незначительный вариант использования, он все же показывает, что выбранный общий доступ к коду может быть полезным.

Для тестов пользовательского интерфейса вам не нужно слишком сильно загрязнять код приложения. Вы можете использовать один аргумент командной строки, чтобы указать, что выполняются тесты пользовательского интерфейса, и использовать его для загрузки некоторых тестовых данных, входа в систему тестового пользователя или выбора конечной точки тестирования для сетевых вызовов. При хорошей архитектуре вам нужно будет выполнить настройку только один раз, когда приложение запускается впервые, а остальной код не замечает, что оно использует тестовые данные (очень похоже на то, что у вас есть среда разработки и производственная среда, между которыми вы переключаетесь для сетевых вызовов). ).

Это именно то, что я делаю в моем текущем iOS проекте, и именно этого я и хочу избежать. Хотя хорошая архитектура может избежать слишком большого количества ошибок c, это все равно загрязняет код приложения. Более того, это не решает ни одного из случаев использования, которые я выделил выше. Предлагая такое решение, вы по существу признаете, что радикальное тестирование черного ящика уступает тестированию серого ящика. Как и во многих сферах жизни, дифференцированный взгляд лучше, чем радикальный « используйте только инструменты, которые мы вам даем, вам не нужно делать это ».

Ответы [ 4 ]

4 голосов
/ 24 января 2020

Тесты пользовательского интерфейса не должны ссылаться на код приложения, они предназначены для имитации постукивания пользователя внутри вашего приложения. Если бы во время этих тестов вы запрыгнули в код приложения, вы бы больше не тестировали то, что делает ваше приложение в реальном мире, вы бы тестировали то, что оно делает, когда им манипулировали так, как никогда не мог сделать пользователь. Тесты пользовательского интерфейса не должны нуждаться в каком-либо коде приложения больше, чем пользователь.

Конечно, для модульных тестов и интеграционных тестов вы используете @testable import …, чтобы получить доступ к любым методам и свойствам, которые не отмечены private или fileprivate. Все, помеченное private или fileprivate все равно будет недоступно из тестового кода, но все остальное, включая internal, будет доступно. Это тесты, в которых вы должны преднамеренно добавлять данные, которые не могут произойти в реальном мире, чтобы убедиться, что ваш код может их обработать. Эти тесты все равно не должны доходить до метода и вносить какие-либо изменения, иначе тест не будет на самом деле тестировать поведение кода.

Вы можете создать столько целей модульных тестов, сколько захотите в проекте, и вы можете используйте одну или несколько из этих целей для проведения интеграционных тестов, а не модульных тестов. Затем вы можете указать, какие цели запускаются в разное время, чтобы ваши более медленные интеграционные тесты не запускались каждый раз, когда вы тестировали и замедляли работу.

В модуле среды и в интеграционных тестах действительно есть все. Вы можете создать экземпляр контроллера представления и вызвать loadViewIfNeeded(), чтобы получить полную настройку представления. Затем вы можете проверить наличие различных торговых точек и запустить их для отправки действий (см. Метод UIControl sendActions(for: )). При условии, что вы настроили необходимые макеты, это позволит вам убедиться, что когда пользователь нажимает кнопку A, вызов отправляется на правильный метод вещи B.

Для тестов пользовательского интерфейса вам не нужно загрязнять ваше приложение. слишком много кода Вы можете использовать один аргумент командной строки, чтобы указать, что выполняются тесты пользовательского интерфейса, и использовать его для загрузки некоторых тестовых данных, входа в систему тестового пользователя или выбора конечной точки тестирования для сетевых вызовов. При хорошей архитектуре вам нужно будет выполнить настройку только один раз, когда приложение запускается, а остальной код не замечает, что оно использует тестовые данные (очень похоже на то, что у вас есть среда разработки и производственная среда, между которыми вы переключаетесь для сетевых вызовов). ).

Если вы хотите узнать больше о тестировании, у Свифта Пола Хадсона есть очень хорошая книга, которую вы можете проверить https://www.hackingwithswift.com/store/testing-swift. Он содержит множество примеров различных видов тестов и полезные советы о том, как их разбить.

Обновление на основе ваших правок и комментариев: Похоже, что вы действительно хотите, это интеграция тесты. Их легко не заметить в мире XCode, поскольку у них нет своей цели для создания. Они используют цель Unit Test, но тестируют несколько вещей, работающих вместе.

При условии, что вы не добавили private или fileprivate ни в одну из своих торговых точек, вы можете создавать тесты в цели Unit Test, которая обеспечивает розетки существуют, а затем вводят текст или запускают свои действия по мере необходимости для имитации навигации пользователя по вашему приложению.

Обычно этот вид тестирования будет просто go от одного контроллера представления ко второму, чтобы проверить, что правильный контроллер представления создается, когда происходит действие, но ничто не говорит, что он не может go дальше.

Вы не получите изображения экрана для неудачного теста, как вы делаете с тестами пользовательского интерфейса, и если вы используете раскадровки, обязательно создайте экземпляры ваших контроллеров представления из раскадровки. Убедитесь, что вы захватываете любые контроллеры навигации и тому подобное, что требуется.

Эта методология позволит вам вести себя так, как будто вы перемещаетесь по приложению, и при этом можете манипулировать любыми данными, которые вам нужны, когда они поступают в различные приложения. методы.

Если у вас есть метод с 10 строками в нем, и вы хотите настроить данные между строками 7 и 8, вам понадобится внешний вызов чего-то поддельного и внести в него изменения или использовать точку останова с командой отладчика, которая вносит изменения. Этот трюк с точкой останова очень полезен для отладки, но я не думаю, что использовал бы его для тестов, поскольку удаление точки останова сломало бы тест.

1 голос
/ 07 мая 2020

Я сталкивался с этой же проблемой ОП. Выход из экосистемы Android и попытка использовать решения для iOS заставят вас задуматься о том, почему Apple так поступает. Это усложняет ситуацию.

В нашем случае мы реплицировали решение для имитации сети для iOS, которое позволяет нам контролировать состояние приложения с помощью файлов ответов stati c. Однако использование для этого автономного прокси-сервера затрудняет запуск XCUITest на физических устройствах. Swift предоставляет некоторые базовые функции Foundations.URLSession, которые позволяют вам делать то же самое изнутри настроенных объектов сеанса. (См. URLProtocolClasses )

Теперь наши тесты пользовательского интерфейса сталкиваются с проблемой IP C, так как приложение бегуна находится в своем собственном процессе. До этого прокси и тесты пользовательского интерфейса жили в одном и том же процессе, поэтому было легко контролировать ответы, возвращаемые для определенных запросов.

Это может быть сделано с использованием некоторых нечетных мостов для межпроцессного взаимодействия, таких как CFMessaging и некоторых других (см. NSHipster здесь )

Надеюсь, это поможет.

1 голос
/ 27 января 2020

Я должен был сделать это для конкретного c приложения. Мы создали некое меню отладки, доступное только для тестов пользовательского интерфейса (используя аргументы запуска, чтобы сделать его доступным) и отображаемое определенным жестом (в нашем случае два касания двумя пальцами).

Это меню отладки это просто всплывающее окно, появляющееся на всех экранах. В этом представлении мы добавляем кнопки, которые позволяют нам обновлять состояние приложения.

Затем вы можете использовать XCUITest для отображения этого меню и взаимодействия с кнопками.

0 голосов
/ 26 января 2020

как именно работает среда модульного тестирования

  • Модульные тесты находятся в пакете, который вводится в приложение.

На самом деле несколько пучков вводятся. Всеобщий пакет XCTest - это фреймворк, называемый XCTest.framework , и вы можете увидеть его внутри встроенного приложения:

enter image description here

Your тесты также являются пакетом, с суффиксом .xctest , и вы также можете видеть это во встроенном приложении:

enter image description here

Хорошо, допустим, вы запрашиваете один или несколько тестов для запуска.

  • Приложение скомпилировано и работает в обычном режиме на симуляторе или устройстве: например, если есть root просмотр иерархии контроллера, он собран нормально, все события времени запуска запускаются так, как они обычно делают (например, viewDidLoad, viewDidAppear, et c.).

  • Наконец пусковой механизм убирает руки. Бегущий по тестам готов ждать этого момента довольно долго. Когда тестовый исполнитель видит, что этот момент наступил, он выполняет исполняемый файл тестового пакета и тесты начинают выполняться. Тестовый код может видеть основной код пакета, поскольку он импортировал основной пакет как модуль, поэтому он связан с ним.

  • Когда тесты завершены, приложение завершает работу. внезапно разрушенный.

Так что насчет тестов пользовательского интерфейса?

Тесты пользовательского интерфейса совершенно разные.

  • Ничего не добавлено в ваше приложение.

  • Первоначально запускается специальное отдельное приложение для выполнения тестов, идентификатор пакета которого назван в честь ваших тестовых наборов с добавлением Runner ; например, com.apple.test.MyUITests-Runner. (Вы даже можете увидеть запуск тестового прогона.)

  • Приложение тестового прогона, в свою очередь, само по себе работает, запускает ваше приложение в собственной специальной среде и управляет им извне используя структуру доступности, чтобы «нажать» кнопки и «увидеть» интерфейс. У него нет доступа к коду вашего приложения; он полностью находится вне вашего приложения, «смотрит» на его интерфейс и только на его интерфейс с помощью специальных возможностей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...