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