Подсказка: тестовые пары - ключ к успеху
Помощник по HTTP-запросам - одна из самых важных частей большинства современных приложений. Он берет на себя всю обработку до и после вызова удаленного API.
В идеале реализация помощника HTTP-запроса должна начинаться после того, как удаленные API будут готовы. Однако в большинстве случаев из-за плотных графиков проекта мобильные разработчики не могут позволить себе роскошь начать свою работу по разработке после того, как удаленные API-интерфейсы будут готовы.
В такой ситуации некоторые разработчики могут пересмотреть свой план разработки, чтобы сначала поработать над функциями, связанными с пользовательским интерфейсом, пока сервер будет готов. Некоторые другие могут установить временный локальный сервер для проверки своего сетевого модуля.
В этой статье я покажу вам свой способ справиться с подобной ситуацией - использовать тестовые двойники для репликации выходных данных удаленных API.
Если вы не знакомы с концепцией тестовых двойников в Swift, ознакомьтесь с этой статьей, в которой подробно объясняется концепция dummy, поддельные, заглушки и имитация.
🔗 Тестовые пары в Swift: Dummy, Fake, Stub, Mock
Лучший способ продемонстрировать, как можно делать заглушки и имитировать удаленный API, - это использовать реальный пример.
Без лишних слов, приступим.
Заявление об ограничении ответственности. Следующий пример может не полностью соответствовать рекомендациям и рекомендациям по проектированию RESTful API. Это сделано для того, чтобы упростить пример.
Общая архитектура
В нашем примере давайте реализуем помощника по HTTP-запросу, который выполняет POST-запрос, который регистрирует нового пользователя на сервере.
На следующей диаграмме показана общая архитектура нашего примера.
Помощник запроса регистрации - это класс, который мы собираемся реализовать. Он выполняет все операции, необходимые перед выполнением запроса POST регистрации, такие как шифрование пароля и кодирование JSON.
Кроме того, он также отвечает за обработку ответов сетевого уровня, таких как декодирование JSON и обработка ошибок.
Помощник шифрования - это служебный класс, отвечающий за шифрование паролей. Вот скелет класса EncryptionHelper
.
Еще один класс, на который следует обратить внимание в нашем примере, - это сетевой уровень. Он содержит весь код, связанный с URLSession
и URLSessionTask
.
Ниже приведен упрощенный скелет класса NetworkLayer
. Обычно он должен содержать другие методы, такие как get()
, put()
и delete()
. Однако в демонстрационных целях мы сосредоточимся только на post()
методах.
Обратите внимание, что фактическая реализация для NetworkLayer
и EncryptionHelper
не важна, важен их протокол. Это потому, что в ближайшее время мы собираемся создать тестовых двойников на основе их протокола.
Предпосылки
Прежде чем мы углубимся в реализацию класса RegistrationRequestHelper
, есть несколько вещей, которые нам нужно убрать с дороги.
- Определите зависимости
RegistrationRequestHelper
. - Завершите структуру запроса JSON.
- Завершите структуру ответа JSON.
- Определите все возможные ошибки
RegistrationRequestHelper
. - Определите модульные тесты для
RegistrationRequestHelper
.
Определите зависимости RegistrationRequestHelper
Как упоминалось ранее в этой статье, мы будем создавать тестовые двойники для проверки RegistrationRequestHelper
, и все зависимости RegistrationRequestHelper
будут тестовыми двойниками, которые нам нужно создать.
Используя диаграмму архитектуры, мы можем легко определить зависимости RegistrationRequestHelper
- NetworkLayer
и EncryptionHelper
.
Завершите структуру запроса JSON
Чтобы реализовать RegistrationRequestHelper
, нам нужно знать структуру JSON запроса. В реальной жизни эту информацию должен предоставить разработчик на стороне сервера.
Кроме того, запрос JSON, скорее всего, будет содержать много информации. Однако для простоты предположим, что структура JSON запроса выглядит следующим образом.
{ "username": "swift-senpai", "password": "abcd1234" }
Завершите структуру ответа JSON
Затем необходимо завершить структуру JSON ответа после успешного вызова API. Эта информация нужна нам, чтобы мы могли правильно реализовать парсер JSON.
Предположим, сервер ответит объектом пользователя, как показано ниже.
{ "user_id": 12345, "username": "swift-senpai", "email": null, "phone": null }
На основе приведенного выше примера JSON мы можем создать класс User
, который соответствует протоколу Decodable
, так что мы можем использовать класс JsonDecoder
для анализа позже.
Определите все возможные ошибки RegistrationRequestHelper
Не забывайте, что возможны ситуации, когда при вызове API возникает ошибка. Таким образом, нашему RegistrationRequestHelper
придется обрабатывать все возможные ошибки, которые могут произойти.
Опять же, для простоты предположим, что есть только 3 возможных ошибки:
- Имя пользователя уже существует - Пользователь предоставил имя пользователя, которое уже существует.
- Неожиданный ответ - не удалось проанализировать ответ JSON.
- Ошибка POST-запроса - все остальные ошибки
Ошибка «имя пользователя уже существует» должна срабатывать, когда мы получаем от сервера сообщение об ошибке JSON. Предположим, что JSON выглядит так, как показано ниже.
{ "error_code": "E001", "message": "Username already exists" }
Ниже приводится перечисление RegistrationRequestError
.
Обратите внимание, что мы согласовали перечисление RegistrationRequestError
с протоколом Equatable
, это особенно полезно, когда мы хотим проверить вывод RegistrationRequestHelper
во время модульного теста.
Определите модульные тесты для RegistrationRequestHelper
Наконец, давайте определим примеры модульных тестов, которые нам нужны, чтобы убедиться, что RegistrationRequestHelper
работает правильно. Ниже приведены необходимые проверки.
- Он отправляет сообщение по правильному URL-адресу.
- Пароль зашифрован перед отправкой.
- Структура JSON запроса верна.
- Ответ JSON анализируется правильно.
- Ошибка
usernameAlreadyExists
обрабатывается правильно. - Ошибка
unexpectedResponse
обрабатывается правильно. - Ошибка
requestFailed
обрабатывается правильно.
С учетом всех предварительных условий пора взяться за дело и погрузиться в реализацию класса RegistrationRequestHelper
.
Реализация
Начнем с реализации инициализатора класса RegistrationRequestHelper
. Мы будем использовать внедрение зависимостей на основе инициализатора, чтобы внедрить и networkLayer
, и encryptionHelper
во вспомогательный класс.
Затем мы добавим метод register()
, который принимает имя пользователя, пароль и обработчик завершения.
Метод register()
зашифрует заданный пароль, закодирует все параметры сообщения в данные JSON и выполнит запрос POST с использованием заданного networkLayer
.
Последняя часть, которую нам нужно реализовать, - это обработчик завершения networkLayer
. Нам придется обрабатывать как success
, так и failure
случай возвращаемого типа Result
.
В случае success
нам нужно обработать 2 типа JSON, пользовательский объект JSON и ошибку JSON. Напомним, вот образцы JSON.
Поскольку мы уже создали класс User
, который соответствует протоколу Decodable
, мы можем легко проанализировать объект пользователя JSON с помощью класса JsonDecoder
.
Для JSON с ошибкой мы можем использовать класс JSONSerialization
для анализа и получения значения error_code
, чтобы мы могли определить, какой тип ошибок происходит.
Если нам не удастся разобрать ответ JSON от сервера, мы вернем ошибку unexpectedResponse
.
В случае failure
мы просто вернем ошибку requestFailed
.
Приведенное выше объяснение может показаться немного сложным, однако приведенный ниже пример кода должен прояснить ситуацию.
На этом мы закончили реализацию RegistrationRequestHelper
. Вот его полная реализация.
Фух! Мы прошли долгий путь в реализации RegistrationRequestHelper
. Пришло время проверить это.
Тестирование
Наконец-то мы подошли к самой важной части этой статьи. Мы будем использовать концепцию тестовых двойников в модульном тесте, чтобы воспроизвести поведение реального рабочего сервера.
Вот краткий обзор того, что мы хотели проверить с помощью модульного теста.
- Он отправляет сообщение по правильному URL-адресу.
- Пароль зашифрован перед отправкой.
- Структура JSON запроса верна.
- Ответ JSON анализируется правильно.
- Ошибка
usernameAlreadyExists
обрабатывается правильно. - Ошибка
unexpectedResponse
обрабатывается правильно. - Ошибка
requestFailed
обрабатывается правильно.
Начнем с первых трех пунктов. Мы сгруппируем их в один тестовый пример XCTest, потому что все эти 3 элемента можно проверить с помощью одних и тех же тестовых двойников. Кроме того, все эти 3 элемента связаны с настройками перед отправкой запроса POST.
Проверки перед запросом POST
В разделе предварительных требований мы уже определили зависимости для класса RegistrationRequestHelper
- networkLayer
и encryptionHelper
. Мы начнем с создания тестовых двойников этих двух классов.
Из фрагмента кода выше видно, что мы объявляем переменные в тестовых двойниках, чтобы отслеживать параметры, которые мы хотели проверить. Мы называем этот вид стратегии модульного тестирования «насмешкой».
Когда тестовые двойники готовы, мы можем приступить к написанию нашего модульного теста. Обратите внимание, что мы будем использовать шаблон AAA (Arrange-Act-Assert) для написания модульного теста.
Здесь нужно обратить внимание на 2 вещи.
Во-первых, обратите внимание, что мы проверяем, что метод mockEncryptionHelper
encrypt(_:)
был вызван. Нас интересует только то, шифруется ли пароль, правильность логики шифрования здесь не важна.
Чтобы проверить правильность логики шифрования, мы должны создать еще один специальный план тестирования для этого, однако это выходит за рамки данной статьи.
Во-вторых, обратите внимание, что для проверки правильности параметров сообщения мы используем строку JSON для утверждения вместо данных JSON. Это связано с тем, что строка JSON более наглядна по сравнению с данными JSON.
Чтобы построить и запустить тест, нажмите ⌘Q. Вы должны увидеть уведомление «Test Succeeded» от Xcode, если вы все выполнили правильно.
Проверки после запроса POST
Теперь давайте проверим, правильно ли проанализирован JSON ответа на объект User
.
Для этого тестового примера нам нужны тестовые двойники - это фиктивные encryptionHelper
и networkLayer
, которые возвращают данные JSON пользовательского объекта.
Обратите внимание, что в приведенном ниже фрагменте кода метод, который мы использовали для принудительного вывода определенного вывода из метода networkLayer
post()
, называется «Заглушка».
Способ использования вышеуказанных тестовых двойников довольно прост.
Используя ту же стратегию заглушки, мы можем перейти к следующему тесту - убедиться, что ошибка usernameAlreadyExists
обрабатывается правильно.
Ниже приведен требуемый тестовый дублер.
Вот тестовый пример.
Как видите, тестовые двойники и тестовые примеры выше очень похожи. Таким образом, я оставлю вам два последних тестовых случая.
- Ошибка
unexpectedResponse
обрабатывается правильно. - Ошибка
requestFailed
обрабатывается правильно.
Используя технику заглушки, которую мы только что обсуждали, вы сможете сделать их без каких-либо проблем.
Если вы застряли в двух последних тестовых случаях, вы можете найти полный пример кода и модульные тесты здесь.
Заключение
Вот и все! Вот как я разработал и протестировал весь класс RegistrationRequestHelper
, вне зависимости от реального рабочего сервера.
Используя стратегии имитации и заглушки в тестовых двойниках, нам удается реплицировать выходные данные удаленных API. Фактически, я бы рекомендовал каждому разработчику выполнять модульные тесты своего сетевого модуля, даже если доступен рабочий сервер.
Вот некоторые другие преимущества, которые вы можете получить при модульном тестировании вашего сетевого модуля:
- Тестовые примеры могут выступать в качестве исполняемой документации.
- Вы чувствуете себя увереннее, поскольку знаете, что ваш код работает правильно.
- Случаи модульного тестирования могут выполняться в любое время даже без подключения к Интернету.
- Случаи модульного тестирования выполняются легко и быстро.
В следующий раз, когда вам понадобится реализовать помощник HTTP-запросов без рабочего сервера, просто помните об этом ...
Дополнительная литература
- Тестовые пары в Swift: манекен, фейк, заглушка, макет
- Лучшие практики модульных тестов в Xcode и Swift
- Модульное тестирование асинхронного кода Swift
Я надеюсь, что эта статья даст вам хорошее представление о том, как можно использовать модульные тесты в повседневной работе по разработке.
Если вам понравилась эта статья, не стесняйтесь делиться ею. Сообщите мне свои мысли в разделе комментариев ниже.
Спасибо за чтение и удачное кодирование. 👨🏼💻
Первоначально опубликовано на https://swiftsenpai.com 11 февраля 2020 г.