TDD и VIPER
Кратко о том что такое TDD
TDD - разработка через тестирование. Предполагается, что в начале пишутся тесты, потом реализация. В случае, если мы закрываем реальные классы протоколами, они идут первыми. После того, как все компоненты написаны - они могут быть интегрированы.
Благодаря такому подходу достигается полное описание ожидаемого поведения класса в тестах. Тесты могут служить в качестве примеров использования класса. Также TDD позволяет нам непредвзято посмотреть на класс с точки зрения пользователя данного класса до его написания.
Более подробно тема TDD была разобрана в статье Андрея Резанова.
VIPER
Обычно разделения на основные слои приложения достаточно для того, чтобы довольно неплохо протестировать сервисный и Core слой, но при наличии "толстого" VC возникают проблемы в тестировании Presentation слоя, и потому многие оставляют его без тестов. Давайте разберемся как VIPER-модули могут помочь нам в покрытии View тестами.
Общим подходом для тестирования является следующий: Окружаем объект тестируемого класса протокол-моками зависимостей. Вызываем методы интерфейса/манипулируем свойствами, проверяем вызовы методов моков.
Тестирование View
Существует некоторое количество библиотек, помогающих в тестировании UI слоя, а с недавних пор у нас появились еще и UI-тесты. Достаточно ли этого для полноценного покрытия View? На мой взгляд - нет.
UI-тесты могут служить неплохим способом написания acceptance, или приемочных тестов. В роли черного ящика выступает все боевое приложение, и мы через интерфейс приложения пытаемся получить тот или иной результат, опять же на интерфейсе.
Проблема с UI-тестами в том, что в случае перегруженного VC весь View является для нас черным ящиком с множеством состояний, и для тестирования необходимо слишком большое количество тестов.
Чем нам тут может помочь VIPER? Из VC выносится логика по подготовке и представлению данных в Presenter. Соответственно на View ложится ответственность лишь за обработку и прокидывание событий в Presenter, а также за отображение UI. Отдельно необходимо заострить внимание на том, что View является не обязательно UI, это класс через который происходит взаимодействие внешнего мира с модулем. Что это означает для нас? Это означает, что IBOutlet и IBAction необходимо делать публичными. Как итог мы получаем возможность протестировать всевозможные нажатия, заполнение полей и прочее без симуляции нажатий, поиска нужных кнопочек по тексту на них и прочих ненадежных вещей.
Отдельно замечу, что Presenter общается с View через протокол.
Подытожим, выделив 2 основных вида тестов:
- Взаимодействуем с IBOutlet/вызываем IBAction -> проверяем, что были вызваны соответствующием методы мока Presenter
- Вызываем методы протокола, через который общается Presenter -> проверяем, что меняются IBOutlet/View
Отдельно можно выделить тестирование методов жизненного цикла VC/V, на которые нам так или иначе необходимо ориентироваться, т.к. зачастую Presenter не может начинать настройку View до viewDidLoad
или viewWillAppear
.
Тестирование Router
Методы роутера вызываются когда необходимо совершить переход из текущего VC. Соответственно тестами покрываются методы переходов/закрытия текущего контроллера. Тестируем вызовы всевозможных аниматоров переходов.
Тестирование Interactor'ов
Interactor является связующим звеном в работе с всевозможными сервисами. Именно через него работа идет работа со Storage, в нем создаются PONSO-объекты.
Но большинство тестов касаются проверки вызовов одних сервисов в зависимости от ответов других.
Тестирование Presenter
Presenter можно назвать связающим звеном модуля, т.к. в ним происходит проксирование запросов от одной части модуля к другой. Тесты на подобную передачу составляют львиную долю от тестов Presenter.
Отдельно замечу, что Presenter является входной точкой для модуля, именно в него передаются данные с предыдущего контроллера. На это опять же необходимо писать тесты.
Presenter является местом, где скорее всего будет максимальное число свитчей и ветвлений. Это также необходимо учитывать, и на вход подавать все возможные принципиально отличающиеся друг от друга комбинации значений.
Тестирование Assembly
Assembly настраивает зависимости компонентов модуля. Его тесты ответственны за то, чтобы проверять, что модуль состоит из правильных частей и все зависимости заполнены.
К счастью, при строгой структуре модуля, данные тесты могут быть созданы автоматически.