TEST-DRIVEN DEVELOPMENT METHODOLOGY OF ARCHITECTURAL COMPONENTS FOR iOS MOBILE APP “THE MOSCOW SERVICES”


Cite item

Full Text

Abstract

The proposed methodology is focused on the iOS mobile app “State services of Moscow” architecture and can be used for the scenes app development. Using this methodology, the three architectural components (View Controller, Interactor and Presenter) for the scene of citizen’s social card registration were developed step by step: scenes in the form of classes, and also unit-tests for these classes were implemented. The written code shows the development features and can be used as an example for creating new scenes. Using the unit-testing methodology of the iOS mobile app “The Moscow Services” allows to minimize the number of defects in the app, reducing the correction defects cost, and also to contribute for cleaner code writing and user-friendly class interfaces development.

Full Text

Введение В настоящее время осуществляется проект разработки мобильного приложения iOS «Госуслуги Москвы», объединяющего востребованные услуги Правительства Москвы. На данный момент проект содержит более чем 1500 файлов с исходным кодом. Кроме того, постепенно в приложение добавляется новая функциональность, вследствие чего увеличивается количество файлов и меняется исходный код, в нем с большой вероятностью появляются регрессионные ошибки (когда работающий функционал сломали неаккуратными изменениями). Действенным решением этой проблемы является модульное тестирование (unit-тестирование) - одна из ключевых практик методологии экстремального программирования. Здесь unit (модуль) - это малый по объему самодостаточный участок кода, реализующий определенное поведение, который часто (но не всегда) является классом. Модульное тестирование заключается в изолированной проверке на корректность и работоспособность каждого отдельного модуля исходного кода - тестирование с высокой гранулярностью. То есть идея состоит в том, чтобы создавать тесты для каждой нетривиальной функции или метода [1]. Это позволяет достаточно быстро проверить, не привело ли очередное изменение кода к регрессии, то есть к появлению ошибок в уже протестированных местах программы, а также облегчает их обнаружение и устранение. В Unit-тестировании используется два подхода к созданию тестов: - разработка через тестирование (test-driven development, TDD); - создание тестов для готовых методов. В данном исследовании применен первый подход. Рассмотрим предлагаемую методику разработки архитектурных компонентов мобильного приложения iOS «Госуслуги Москвы» через тестирование. Эта техника основана на итеративном цикле разработки: написание теста, покрывающего желаемое изменение, затем написание кода, который позволит пройти тест, далее рефакторинг нового кода к соответствующим стандартам. Так формируется устройство классов и модулей, в то время как все изменения в коде направляют модульные тесты [2-3]. Для написания исходного кода и кода тестов модулей выбран язык программирования Swift, применяется встроенная библиотека XCTest (предназначена для создания и запуска модульных тестов производительности и тестов пользовательского интерфейса), поддерживаемая средой Xcode. В Xcode все создаваемые приложения возможно тестировать без необходимости загрузки программ на реальные устройства. Архитектура приложения Предлагаемая методика ориентирована на архитектуру приложения iOS «Госуслуги Москвы» и может применяться при разработке сцен приложения. На рисунке 1 изображены архитектурные модули приложения. Большинство экранов приложения реализовано с использованием архитектуры Clean Swift. Рассмотрим VIP цикл в архитектуре Clean Swift. View Controller, Interactor, Presenter - это три основных компонента сцены в CleanSwift, каждый из которых ответственен за выполнение своей задачи. Interactor содержит бизнес-логику и выполняет работу над данными, совершает сетевые запросы. Presenter осуществляет форматирование данных. View Controller работает с графическим интерфейсом и отображает данные пользователю. Рисунок 1. Архитектура Clean Swift Типичный сценарий при этом включает следующие действия. 1. Пользователь нажимает на кнопку в пользовательском интерфейсе приложения. 2. Жест нажатия попадает в View Controller через IBAction. 3. View Controller создает объект запроса (request object) и отправляет его в Interactor. 4. Interactor получает объект запроса, выполняет работу, создает объект ответа, помещает в него результат работы, отправляет объект ответа (response object) в Presenter. 5. Presenter получает объект ответа, форматирует результаты, помещает результаты в модель отображения (view model), отправляет модель отображения компоненту View Controller. 6. View Controller отображает результат пользователю. Блок-схема алгоритма методики Для описания разработанной методики использована блок-схема алгоритма действий, выполняемых программистом при разработке сцены приложения (см. рисунок 2). В блоках 2-17 описана последовательность действий подготовки классов, необходимых для написания кода тестов. В блоках 2-4 создаются три класса, являющиеся архитектурными модулями сцены: (Имя сцены) Interactor, (Имя сцены) View Controller, (Имя сцены) Presenter Spy. В блоке 5 архитектурные модули связываются между собой через протоколы. Протоколы описывают, какие данные и сообщения могут передавать друг другу архитектурные модули. Класс (Имя сцены) Interactor соответствует протоколу выхода (Имя сцены). В данном исследовании применен первый подход. Рассмотрим предлагаемую методику разработки архитектурных компонентов мобильного приложения iOS «Госуслуги Москвы» через тестирование. В модуле View Controller класс (Имя сцены) соответствует протоколу выхода (Имя сцены) Presenter. Класс (Имя сцены) Presenter соответствует протоколу выхода (Имя сцены) Interactor. В блоках 6-8 создаются классы, содержащие тесты. Один класс тестов соответствует одному архитектурному модулю. Например, класс (Имя сцены) PresenterTests содержит тесты для модуля (Имя сцены) Presenter. В блоках 9-11 определяются классы-шпионы (Spy) в каждом классе, содержащем тесты. Так как архитектурные модули соединены между собой, используя объекты-шпионы, можно отслеживать данные, передаваемые между модулями. В классе (Имя сцены) Interactor Tests объявляется класс шпион (Имя сцены) Presenter Spy. В классе (Имя сцены) PresenterTests объявляется класс-шпион (Имя сцены) View Controller Spy. В классе (Имя сцены)View Controller Tests объявляется класс-шпион (Имя сцены) Interactor Spy. В блоках 12-14 в классах тестов создаются ссылки типа тестируемых объектов, то есть класс (Имя сцены) PresenterTests класс будет содержать ссылку на (Имя сцены) Presenter. В блоках 15-17 описываются методы Setup (Имя сцены) Presenter, Setup (Имя сцены) View Controller, Setup (Имя сцены) Interactor. Методы предназначены для базовой настройки тестовых объектов и вызываются перед запуском тестовых методов. Это позволяет не загромождать код самих тестов и избавляет от повторения кода. В данных методах происходит создание тестируемого объекта архитектурного модуля, присвоение ссылок, созданных в пунктах 12-14 на созданный объект. Создается объект-шпион и устанавливается выходом для объекта архитектурного модуля. Возможны и другие настройки специфичные для объекта. В блоке 18 происходит выбор одного из архитектурных модулей для реализации. Порядок реализации не принципиален. Блоки 19-33 содержат цикл разработки тестов и рабочего функционала для выбранного архитектурного модуля. Алгоритм считается завершенным, когда для всех трех модулей реализован полный набор тестов и рабочий код, который дает положительный результат прохождения тестов. Разработка сцены приложения регистрации социальной карты москвича С целью демонстрации принципов работы разработанной методики описан процесс разработки сцены приложения и ее покрытия тестами. Разработанная сцена предназначена для регистрации пользователем социальной карты москвича (СКМ) (см. рисунок 3). Рассмотрим пользовательский сценарий. Предусловие: пользователь авторизован в приложении. Надпись: Рис. 2. Блок-схема алгоритма методики Рис. 3. Экраны регистрации карты, ввода номера карты, информации о вводе номера СКМ, успешной регистрации 1. Пользователь на экране услуги СКМ инициирует регистрацию СКМ. 2. Клиент отображает экран регистрации СКМ: номер телефона по умолчанию заполнен номером телефона из профиля пользователя и недоступен для редактирования. 3. Пользователь вводит в поле номер и серию карты одним из следующих способов: - пользователь вводит вручную (в полиграфическом виде): клиент отображает введенный номер на экране и преобразует его в канонический вид для отправки в запросе; - пользователь сканирует карту: клиент получает номер в каноническом виде, преобразует его в полиграфический вид и отображает в поле ввода на экране. 4. Пользователь продолжает процесс регистрации; 5. Клиент осуществляет запрос методом registrCard (зарегистрировать карту) и обрабатывает ответ сервера: - если регистрация прошла успешно, переход на следующий шаг; - если сервер вернул ошибку регистрации, клиент отображает диалоговое окно с сообщением об ошибке. 6. Отображает главный экран услуги СКМ с данными привязанной карты. Завершение сценария. Модуль Interactor должен содержать следующую функциональность: - загружать номер телефона пользователя из профиля пользователя; - преобразовывать номер пользователя в канонический вид из полиграфического вида; - осуществлять запрос на регистрацию карты. Разработан шаблон класса CardRegistrationInteractorTests, содержащий следующие тестовые методы: class CardRegistrationInteractorTests: XCTestCase { // MARK: Subject under test var sut: GUCardRegistrationBusinessLogic! // MARK: Test lifecycle override func setUp() { super.setUp() setupCardRegistrationInteractor() } override func tearDown() { super.tearDown() } // MARK: Test setup func setupCardRegistrationInteractor() { } } Класс Card Registration Interactor Tests предназначен для тестирования рабочего класса GU-Card Registration Interactor. Перед вызовом каждого тестового метода будет выполнен метод setUp. После того, как тестовый метод завершит работу, будет вызван метод tearDown. Метод setup Card Registration Interactor () предназначен для инициализации начальных значений. Переменная sut (System under test) - это ссылка на экземпляр тестируемого объекта. Для реализации тестов необходимо симулировать контекст, в котором выполняются тестируемые функции, для этих случаев существует понятие mock-объектов (spy-объект), представляющих собой специально созданные сущности, единственная цель которых - это симуляция контекста, в котором выполняется тестируемая функция. Моком называется тестовый двойник, способный запоминать аргументы вызовов своих методов, количество вызовов, часто также способный самостоятельно проводить проверки. Как правило, каркасы изоляции позволяют наделять моки дополнительным настраиваемым поведением вплоть до полной имитации работы класса с подменой одного небольшого аспекта [4]. Продемонстрируем использование mock-объектов и объектов-шпионов при тестировании модулей приложения. Далее отображены классы этих объектов. В классе GU-Card Registration Presentation Logic Spy (используется для мониторинга результата работы тестируемого класса) переопределены базовые методы. Шпион отслеживает, были ли вызваны его методы, анализирует значения переданных параметров: class GUCardRegistrationPresentationLogicSpy: GUCardRegistrationPresentationLogic { var presentCardRegisteredCalled = false var presentedErrorStringCalled = false var presentErrorCalled = false var presentPhoneCalled = false var displayModule: CommonDisplayLogic? func presentCardRegistered() { presentCardRegisteredCalled = true } func present(errorString: String, cancelTitle: String, completion: (() -> Void)?) { presentedErrorStringCalled = true } func presentError(error: Error, completion: (() -> Void)?) { presentErrorCalled = true } func present(phone: String) { presentPhoneCalled = true } } GUCardRegistrationServiceSpy эмулирует поведение класса GUCardRegistrationMainService: class GUCardRegistrationServiceSpy: GUMSCService { func getBarCode(cardId: String, completion: @escaping ((GUResult<MSCGetBarCodeModel>) -> Void)) {} func getCard(completion: @escaping ((GUResult<GUMoscowSocialCard>) -> Void)) {} func verifyCard(order: String, phone: String, completion: @escaping ((GUResult<GUMscVerifyCardItem>) -> Void)) {} func removeCard(withId cardId: String, completion: @escaping ((Error?) -> Void)) {} func submitCard(cardSeries: String, cardNumber: String, completion: @escaping ((GUResult<GUMoscowSocialCard>) -> Void)) { if (cardNumber == "19110188" || cardSeries == "19110188") { completion(.failure(NSError(domain: "asdasd", code: 213, userInfo: nil))) } completion(.success(GUMoscowSocialCard())) } } В соответствии с разработанной методикой для базовой настройки тестовых объектов используются методы с префиксом setup. Они вызываются перед запуском тестовых методов. Это позволяет не загромождать код самих тестов и избавляет от повторения кода. Разработаны примеры реализации методов инициализации для всех видов архитектурных модулей. Инициализируем свойства Interactor объектами-шпионами: class CardRegistrationInteractorTests: XCTestCase { // MARK: Subject under test var sut: GUCardRegistrationBusinessLogic! var presenter: GUCardRegistrationPresentationLogicSpy! // MARK: Test lifecycle override func setUp() { super.setUp() setupCardRegistrationInteractor() } func setupCardRegistrationInteractor() { let interactor = GUCardRegistrationInteractor() presenter = GUCardRegistrationPresentationLogicSpy() interactor.presenter = presenter interactor.cardService = GUCardRegistrationServiceSpy() interactor.card = GUMSCCardRegistration(canonical: «9643907703025200065 19110177») sut = interactor } Продемонстрируем реализацию тестовых методов для всех архитектурных модулей. В качестве примера приведем разработанные тесты для модуля Presenter: // MARK: Test doubles func testPhonePresented() { var phoneToDisplay = "21231" sut.present(phone: phoneToDisplay) XCTAssertTrue(viewControllerSpy.phoneDisplayed == phoneToDisplay) } func testCardRegisteredPresented() { sut.presentCardRegistered() XCTAssertTrue(viewControllerSpy.cardRegisteredDisplayed) } Все unit-тесты выполняют непосредственную сверку полученных результатов с ожидаемыми при помощи предопределенных функций библиотеки тестирования, возвращающих положительный или отрицательный результат. Примером такой функции может служить XCTAssert - вариант проверяющей функции, успешно завершающейся, если входной логический параметр действителен. Заключение Использование методики модульного тестирования мобильного приложения iOS «Госуслуги Москвы» позволяет уменьшить количество дефектов в приложении, уменьшает стоимость исправления дефектов, способствует написанию более чистого кода и проектированию удобных интерфейсов классов. В процессе разработки было обнаружено, что из-за большого числа файлов в проекте набор тестов запускается недостаточно быстро, что замедляет скорость разработки. В перспективе следует произвести поиск возможных программных и аппаратных способов увеличения быстроты сборки проекта и запуска набора тестов.
×

About the authors

Mariya Anatolevna Bogomolova

Povolzhskiy State University of Telecommunications and Informatics

Email: bogomolova-ma@psuti.ru

Sergey Alexandrovich Klementyev

Povolzhskiy State University of Telecommunications and Informatics

Email: klementyevsa@yandex.ru

References

  1. Орлов С.А., Цилькер Б.Я. Технологии разработки программного обеспечения. СПб.: Питер, 2012. - 608 с.
  2. Бек К. Экстремальное программирование: разработка через тестирование. СПб.: Питер, 2003. - 224 с.
  3. Beck K. Extreme Programming Explained: Embrace Change. Boston: Addison Wesley, 2004. - 224 p.
  4. Osherove R. The Art of Unit Testing with Examples in.NET. Greenwich: Manning, 2009. - 324 c.
  5. Meszaros G. xUnit Test Patterns. Refactoring Test Patterns. Boston: Addison-Wesley, 2007. - 948 c.
  6. Симан М. Внедрение зависимостей в.NET. Спб.: Питер, 2013. - 464 с.
  7. Фаулер М. Шаблоны корпоративных приложений. М.: Вильямс, 2011. - 544 с.
  8. Фаулер М. Рефакторинг. Улучшение существующего кода. СПб.: Символ-плюс, 2010. - 432 с.
  9. Astels D. Test Driven Development: A Practical Guide, Upper Saddle River. NJ: Prentice Hall PTR, 2003. - 592 p.
  10. Schmitt W. Automated Unit Testing of Embedded ARM Applications // Information Quarterly. - 2014. - Vol. 3. - No. - P. 29.

Supplementary files

Supplementary Files
Action
1. JATS XML

Copyright (c) 2019 Bogomolova M.A., Klementyev S.A.

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

This website uses cookies

You consent to our cookies if you continue to use our website.

About Cookies