Подсказка: тестовые пары - ключ к успеху

Помощник по 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, есть несколько вещей, которые нам нужно убрать с дороги.

  1. Определите зависимости RegistrationRequestHelper.
  2. Завершите структуру запроса JSON.
  3. Завершите структуру ответа JSON.
  4. Определите все возможные ошибки RegistrationRequestHelper.
  5. Определите модульные тесты для 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 возможных ошибки:

  1. Имя пользователя уже существует - Пользователь предоставил имя пользователя, которое уже существует.
  2. Неожиданный ответ - не удалось проанализировать ответ JSON.
  3. Ошибка 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. Фактически, я бы рекомендовал каждому разработчику выполнять модульные тесты своего сетевого модуля, даже если доступен рабочий сервер.

Вот некоторые другие преимущества, которые вы можете получить при модульном тестировании вашего сетевого модуля:

  1. Тестовые примеры могут выступать в качестве исполняемой документации.
  2. Вы чувствуете себя увереннее, поскольку знаете, что ваш код работает правильно.
  3. Случаи модульного тестирования могут выполняться в любое время даже без подключения к Интернету.
  4. Случаи модульного тестирования выполняются легко и быстро.

В следующий раз, когда вам понадобится реализовать помощник HTTP-запросов без рабочего сервера, просто помните об этом ...

Дополнительная литература

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

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

Спасибо за чтение и удачное кодирование. 👨🏼‍💻

Первоначально опубликовано на https://swiftsenpai.com 11 февраля 2020 г.