#ios #swift #xcode #unit-testing #permissions
#iOS #swift #xcode #модульное тестирование #разрешения
Вопрос:
У меня есть модульный тест, который вызывает методы CNContactStore()
, например CNContactStore().execute(saveRequest)
. Таким образом, появляется диалоговое окно разрешений для контактов, например, уведомление о push-уведомлениях, но диалоговое окно разрешений контактов не закрывается автоматически. Я знаю, как это сделать в тестах пользовательского addUIInterruptionMonitor()
интерфейса, но понятия не имею, как это сделать в модульном тестировании.
Ответ №1:
Я бы создал оболочку, CNContactStore
а затем использовал макет при тестировании.
На самом деле вы не заинтересованы в тестировании CNContactStore
, вы заинтересованы в тестировании, с которым ваш код взаимодействует CNContactStore
правильно, верно?
Настройка
Я бы начал с создания протоколов и классов для извлечения содержимого контактов из вашей «обычной» базы кода.
Сначала Contact
структура для хранения свойств, которые вам понадобятся позже для создания фактического CNContact
struct Contact {
//holds whichever properties you need to create a CNContact
}
Затем протокол для хранения методов, которые вы хотели бы выполнить. Это можно сделать с помощью протокола с множеством методов, подобных so
protocol ContactsHolder {
func save(contact: Contact)
func add(contact: Contact)
func delete(contact: Contact)
func update(contact: Contact)
//Maybe more methods, the important thing is that you abstract yourself away from CNContactStore and other Contact kit classes
}
Или вы могли бы создать enum
, содержащий возможные варианты, например
enum ContactsUpdateMethod {
case save(Contact)
case add(Contact)
case delete(Contact)
case update(Contact)
}
protocol ContactsHolder {
func execute(_ method: ContactsUpdateMethod)
}
В вашем «реальном» коде
После этого вы готовы создать свой фактический ContactsHolder, который затем использует внутренне CNContactStore
и все, что связано с этой структурой.
Так, например (если вы выбрали версию с «чистой» save
функцией)
class CNContactsHolder: ContactsHolder {
func save(contact: Contact) {
//1. create a `CNContact` from your `Contact`
//2. create a saveRequest
//3. execute: CNContactStore().execute(saveRequest)
}
....
}
И затем вы предоставляете классам, которые должны работать со CNContactStore
ссылкой на ваш новый ContactsHolder
протокол
Итак, в вашем классе у вас есть
let contactsHolder: ContactsHolder
И затем вы можете либо передать его в своем init
методе
init(contactsHolder: ContactsHolder = CNContactsHolder()) {
self.contactsHolder = contactsHolder
}
Или вы можете объявить его как a var
, а затем присвоить ему значение по умолчанию
Поэтому вместо:
let contactsHolder: ContactsHolder
Вы говорите:
var contactsHolder: ContactsHolder = CNContactsHolder()
Важно то, что вы можете изменить значение ContactsHolder
с «реального» CNContactsHolder
на макет, когда вам нужно протестировать
В вашем тестовом коде
Чтобы проверить это, вы создаете макет:
struct MockContactsHolder: ContactsHolder {
var saveWasCalled = false
func save(contact: Contact) {
saveWasCalled = true
}
}
И затем вы используете это в своем классе вместо CNContactsHolder
Теперь вы должны иметь возможность тестировать свой собственный код, не прерываясь из-за разрешений и прочего, что не имеет отношения к вашему коду, но является следствием использования CNContactStore
.
Отказ от ответственности 🙂
Я не запускал вышеуказанное с помощью компилятора, поэтому могут быть опечатки.
Кроме того, могут отсутствовать фрагменты, чтобы сделать его подходящим CNContact
(обратные вызовы и т. Д.), Но я надеюсь, вы поняли, как разделить вещи.
И finally…it может показаться, что это большая работа, но я думаю, что имеет смысл выделить «специфичный для фреймворка» код в отдельный вспомогательный класс, спрятанный за протоколом, чтобы вы могли менять его всякий раз, когда вам нужно, например, провести тестирование или … если вы решитеизбавьтесь CNContact
от него позже и используйте другие фреймворки.
Надеюсь, это поможет.
Комментарии:
1. Хотя я думаю, что это очень хороший ответ, и это определенно тот подход, который я бы рекомендовал для тестирования материалов, запрашивающих разрешения, я хотел бы отметить, что вопрос был конкретно об отклонении предупреждений. Есть идеи?
2. @XavierLowmiller Спасибо за ваши добрые слова. Что касается вашего вопроса. К сожалению, нет. Я не вижу, как вы можете программно отклонить предупреждение от тестовой цели, которая не «поддерживает пользовательский интерфейс» (именно поэтому я предложил другой подход). Сейчас я просто размышляю вслух… Есть ли у вас доступ к
ViewController
вашему тестовому коду и можете ли вы оттуда получить доступ кUIWindow
orUIApplication
и оттуда отключить оповещение?
Ответ №2:
Я думаю, вы путаете модульное тестирование с тестированием пользовательского интерфейса. В модульном тестировании вы просто хотите протестировать свои коды (например, функции и свойства), и для этого вам, скорее всего, понадобится «макет».
Например, вы хотите протестировать свой селектор кнопок входа в систему, который имеет сетевые вызовы после проверки полей ввода.
Следующие шаги должны быть следующими:
- Проверьте свою логику проверки. Как неудачные, так и последующие случаи.
- Протестируйте код внутри блока завершения вашего вызова API, НО не используя РЕАЛЬНЫЕ данные API. Вместо этого используйте свой mocked API здесь.
- и так далее…
Теперь, возвращаясь к вашему вопросу, вам не нужно обрабатывать этот неконтролируемый и «не подлежащий отклонению» контроллер оповещений, созданный системой. Вместо этого вы хотите «смоделировать» (тьфу, не снова) это всплывающее событие, нажав функцию делегирования для этого предупреждения о доступе к контактам со стороны системы, «смоделировать» ответ, а именно «Не разрешать» и «ОК». Что, по-вашему, произойдет, когда пользователь нажмет на первую кнопку? Вторая кнопка? Установите ожидания / утверждение.
Вот и все. Нажмите на каждую функцию, которую вам нужно нажать, чтобы увеличить охват вашего кода. Дайте мне знать, если это поможет.