В модульных тестах, как я могу программно закрыть диалоговое окно системных разрешений?

#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 or UIApplication и оттуда отключить оповещение?

Ответ №2:

Я думаю, вы путаете модульное тестирование с тестированием пользовательского интерфейса. В модульном тестировании вы просто хотите протестировать свои коды (например, функции и свойства), и для этого вам, скорее всего, понадобится «макет».

Например, вы хотите протестировать свой селектор кнопок входа в систему, который имеет сетевые вызовы после проверки полей ввода.

Следующие шаги должны быть следующими:

  1. Проверьте свою логику проверки. Как неудачные, так и последующие случаи.
  2. Протестируйте код внутри блока завершения вашего вызова API, НО не используя РЕАЛЬНЫЕ данные API. Вместо этого используйте свой mocked API здесь.
  3. и так далее…

Теперь, возвращаясь к вашему вопросу, вам не нужно обрабатывать этот неконтролируемый и «не подлежащий отклонению» контроллер оповещений, созданный системой. Вместо этого вы хотите «смоделировать» (тьфу, не снова) это всплывающее событие, нажав функцию делегирования для этого предупреждения о доступе к контактам со стороны системы, «смоделировать» ответ, а именно «Не разрешать» и «ОК». Что, по-вашему, произойдет, когда пользователь нажмет на первую кнопку? Вторая кнопка? Установите ожидания / утверждение.

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