- Android Unit Testing Clean Code Architecture with MVVM
- Application Architecture
- Important dependencies for the project
- Dependency Injection with Koin
- MainApp.kt
- MainDIComponent.kt
- LoginActivityFragment.kt
- LoginActivityViewModel.kt
- LoginUseCase.kt
- LoginRepository.kt
- LoginAPIService.kt
- Unit Testing
- Android mvvm unit test
- 5 января 2017 г. Реализовываем MVVM в Android
- Что такое MVVM?
- И все же — кто есть кто?
- Делать — так наверняка
- Несколько полезных примеров
- Забудем о boilerplate!
- Резюмируя
Android Unit Testing Clean Code Architecture with MVVM
Dec 20, 2019 · 8 min read
Unit testing Android Application while libraries getting upgraded and added into Android eco system is definitely a challenge.😱
Similar is the story with Android + MVVM + Uncle Bob’s Clean Code Architecture.
This post is targeted to address data flow with Clean Code Architecture and MVVM along with testing (UT + UI aka Instrumentation Testing)
Main Libraries Used
Full Source code can be found in this Github Project
Here I will be mainly targeting h ow to test the data flow with respect to different components in the app. Which are the following points. Anybody can easily adopt the following technique in apps. Even for complex apps, underlying idea can remain the same.😇
- View Model ( or consider this as an Interactor from View. Responsible for taking inputs from View and delivering back the processed response)
- Use Case ( There can be one or multiple use case flows in single View Model. Handles specific data manipulation for a flow and connecting with Repository for the same)
- Repository ( Either connects to REST API or Data Base)
Note: REST API provided by https://swapi.dev will be used in this article for showing sample data retrieval. Thanks to SWAPI.
The complete API documentation detail can be found over https://swapi.dev/documentation
Application Architecture
Let us target to consider a common data flow.
User launches app. App hits a URL and get back data. With which View populates a List View.
From the Android Guide to app architecture , slight change for use case can get added for the separation of concerns. Which will give us the following architecture diagram. This article will be discussing the development details of the below architecture along with testing.
Targeted screen/fragment, view will have corresponding View Model class instance incorporated through Kotlin lazy. A method in ViewModel(VM) will be invoked to initiate data retrieval with few/no parameters.
VM will have reference to the respective Use Case class passed from Fragment received through Dependency Injection. With this reference , VM will invoke method in Use Case to execute corresponding use case flow (Eg:- Login request).
Use case will receive this call, if any business logic needs to be performed, use case will handle and similarly ,with the help of Repository reference received via Dependency Injection, use case invokes method in repository for getting data.
Repository will in-turn get in touch with Network API ( Retrofit/ Any similar Networking Client) and retrieves the data. Or connect to local Data Base and retrieves data.
NOTE: In this flow, nowhere we are having cyclic reference of other components.
Now, getting back to the data flow. Once Repository receives data, it will decide whether data needs to be saved in local DB (Eg:- Room ORM) or just get back to caller. If the data needs to be reverted back to caller, the same will get passed over to View through the above explained flow in reverse order😀. Kotlin coroutines makes this flow relatively simple with help of suspending functions as shown below.
Important dependencies for the project
build.gradle will have the following. Complete dependencies can be found in Github Project
Dependency Injection with Koin
First part is to get our Dependency Injection (DI) targets and corresponding classes ready to be injected.
- Network ( Retrofit)
- View Model
- Use Case
- Repository
All these dependencies will be injected to main application class as follows
MainApp.kt
MainDIComponent.kt
Concludes all dependencies required throughout the application. (List of Koin Dependencies )
LoginActivityFragment.kt
In view , the View Model will be injected and used as follows.
LoginActivityViewModel.kt
View Model will have Use Case as dependent class received from corresponding Fragment. Which will invoke specific method as shown below.
LoginUseCase.kt
Use Case will pass on the incoming request to Repository and handle any business logic required once data gets back from Repository.
LoginRepository.kt
Repository will initiate Network Request and get back data from REST End Point.
LoginAPIService.kt
The data received will be send back to UseCase. UseCase, as mentioned earlier will handle any business logic required and revert back processed response to ViewModel. ViewModel will update the live data object, which will be observed by View class. View in turn updates the data correspondingly.
Unit Testing
So now, coming to testing. 😵 Questions which would arise are. 🤔
- What classes need to be tested.
- How can dependencies be 💉injected for testing.
- How response will be mocked? If then what libraries needed to be used.
- How will the mocked response be accessible to test classes.
- Do I need to check only flow till my business logic hits Retrofit classes or should I test Retrofit classes as well.
As the post is concentrating on clean code +MVVM data flow testing, I will concentrate on data flow areas only to keep the article less complicated. Ideally all the written code should be tested. By following TDD ( Test Driven Development) all other classes developed can be targeting for testing. Moreover neither developer will implement logic that is not testable nor include any code which is not required to satisfy the requirements.
First and foremost point.
❌🛑 In the mentioned data flow ,no classes other than Fragment/Activity should have reference to view.❌🛑
Having references of view in ViewModel or other classes are not at all encouraged. Moreover this makes the code Non Testable.
- Getting the dependencies ready for testing
In-order to override and get specific dependencies for testing, we can create similar dependencies in test folder and make it available for test classes through following method.
Through MainDIcomponentTest.kt we can have custom network dependency, MockWebServer and others injected to test classes using Koin. Test classes can invoke this as well.
The NetworkDITest.kt will provide network dependency were we can pass the URL while testing with MockWebServer specific endpoint.
With these, our dependencies for Unit Testing is completed.
2. Testing Repository Class
For unit testing, we will create a classes for basic configuration’s. Each unit test class will extend this base class which intern will be extending KoinTest class. Refer BaseUTTest.kt for the details.
While requesting for REST API , we can get back mocked response using MockWebServer. This response will be received from locally saved json file.
Compare the expectation with received response (Assertion) . We can see that test is passed and UT gives us a ✔️ 😍 Hoorrraaayyyy 🎉🕺🏆
3. Testing Use Case class
In the similar lines, we can target LoginUseCase’s processLoginUseCase method for testing.
We can get this test case also passed and gives us ✔️ 😍
3. Testing View Model class
Here we will use Mockk’s libraries coEvery method for mocking the expected response. To initiate mockk use the following.
And now we can have our test case with the following. And run the test.
This completes testing the data flow of Clean Code Architecture.
I have kept the architecture as simple as possible for a beginner to grasp and start with. There are improvements and abstraction possible in many areas, which you can try out😉
With this I will give a pause and continue Instrumentation testing part in next post !
Do clap 👏 if you find this article helpful.
Full Source code can be found in this Github Project
Thanks for Reading ! Happy Coding 🎉Cheers 🤜🤛🥂
Источник
Android mvvm unit test
To learn android MVVM based on Android Architecture Components and unit test.
- Kotlin ❤️
- Start with basic and simple.
- Each example should has a unit test at least or more.
E01 — Simple Activity
Simple Unit Test with Espresso.
E01a — Simple Activity and Two-way binding
MediatorLiveData to merge events of two LiveData
E02 — Simple Fragment with some animations
Some animations with lottie(resources by Eddy Gann).
Custom binding attribute onAnimationEnd using BindingAdapter.
For single Fragment android instrumented test, there is a dummy activity which only works on debug and test that implemented with debug Source set and @RestrictTo annotation.
For unit test, it needs to decouple the logic that getting boolean result from ViewModel. so ViewModelProvider.Factory is here.
E02a — Simple Fragment with some animations and DI library
Dependency Injection with Kodein DI as a elegant way to decouple the logic.
Implementation of Add, Remove and Edit actions.
E04 — Request Permission
Transformaions.map to convert strings to a simple format.
E05 — Interaction between 2 Fragments
A Fragment knows two ViewModel , so two views observe one ViewModel .
E06 — Infinite RecyclerView
E07 — Change Screen
Using Coordinator for ViewModel . By doing so, changing screen logic are in one place.
Источник
5 января 2017 г. Реализовываем MVVM в Android
Пришло время научиться делать гибкую архитектуру для Android, используя DataBinding!
Привет всем! Прежде всего хотелось бы извиниться за 9 месяцев затишья с момента публикации статьи о DataBinding. Все никак не хватало времени написать обещанное продолжение. Но в этом есть и плюсы — за это время мы успели “обкатать” некоторые решения и сделать их еще лучше 😉
Что такое MVVM?
Для начала давайте рассмотрим классическое описание этого шаблона и разберем каждый из его компонентов. Итак, Model-View-ViewModel (т.е. MVVM) — это шаблон архитектуры клиентских приложений, который был предложен Джоном Госсманом (John Gossman) как альтернатива шаблонам MVC и MVP при использовании технологии связывания данных (Data Binding). Его концепция заключается в отделении логики представления данных от бизнес-логики путем вынесения её в отдельный класс для более четкого разграничения.
Теперь давайте разберемся, что же значит каждая из трех частей в названии:
- Model — это логика, которая связанная с данными приложения.
- Другими словами — это POJO, классы работы с API, базой данных и пр.
- View — собственно, это и есть layout экрана, в котором располагаются все необходимые виджеты для отображения информации.
- ViewModel — объект, в котором описывается логика поведения View в зависимости от результата работы Model. Можно назвать его моделью поведения View. Это может быть как форматирование текста, так и логика управления видимостью компонентов или отображения состояний, таких как загрузка, ошибка, пустые экраны и т.д. Также в ней описывается поведение, которое было инициировано пользователем (ввод текста, нажатие на кнопку, свайп и т.п.)
Что же это дает нам в конечном итоге?
- Гибкость разработки. Этот подход повышает удобство работы в команде, т.к. пока один член команды работает над компоновкой и стилизацией экрана — другой, в это время, описывает логику получения данных и их обработки;
- Тестирование. Такая структура упрощает написание тестов и процесс создания mock-объектов. Также, в большинстве случаев отпадает потребность в автоматизированном UI-тестировании, т.к. можно обернуть unit-тестами сам ViewModel;
- Разграничение логики. За счет большего разграничения код становится более гибким и простым в поддержке, не говоря о его читабельности. Каждый модуль отвечает за свою конкретную функцию и только.
Так как ничего идеального не бывает, есть и недостатки:
- Для небольших проектов этот подход может быть неоправданным.
- Если логика привязки данных слишком сложная — отлаживать приложение будет немного труднее.
И все же — кто есть кто?
Изначально, в Android этот паттерн нуждается в небольшой модификации. Точнее сказать, нужно пересмотреть сами компоненты и их привычное восприятие.
Рассмотрим, к примеру, Activity. У нее есть layout-файл (XML) и связанный с ней Java-класс, в котором мы описываем все, что касается её работы. Получается, что xml-файл — это View, а java-класс, соответственно, ViewModel? Похоже на то, но как бы не так. А что, если я скажу, что наш класс — это тоже View? Ведь у custom view тоже есть xml и класс-обработчик, но он считается одним целым? Более того, как в активности, так и в custom view и вовсе можно обойтись без xml-файла, при этом создавая все необходимые виджеты из кода. Вот так и получается, что в нашей архитектуре View == Activity (т.е. XML + Java-class).
Но что же тогда ViewModel и, самое главное, где его размещать? Как мы могли видеть в одном из разделов предыдущей статьи, это совершенно отдельный объект. И именно его мы передавали в xml-файл используя binding.setViewModel() . В нем-то и будут поля и методы, которые нужны нам для связывания моделей с View.
Model же у нас никак не отличается от традиционного её понимания. Единственное, что хотелось бы добавить от себя — не делайте обращения к базе или API прямо во ViewModel. Вместо этого для каждого VM лучше создавать Repository — тогда код будет чище и менее громоздкий.
Таким образом мы получаем следующее: класс активности «обслуживает» только ту логику, которая относится непосредственно к View, но никоим образом не касается его поведения. К таким случаям можно отнести установку Toolbar или надстройки над TabLayout и Viewpager. Немаловажно, что только из View можно обращаться к виджетам напрямую по id ( binding.myView.doSomething() ), т.к. VM не должна знать совершенно ничего о View — коммуникация между ними реализовывается только посредством Binding. Самой же логикой загрузки данных и их отображения занимается ViewModel, а алгоритм получения данных описывается, соответственно, в Model.
Наш дизайнер ушел в отпуск, поэтому схема будет с элементами новогоднего настроения 🙂
Делать — так наверняка
Давайте перейдем непосредственно к реализации. Посмотрев на схему выше можно заметить, что View передает ViewModel не только команды (действия пользователя), но и её жизненный цикл. Почему? Потому что, отчасти, это тоже своего рода действия, которые инициируются пользователем. Ведь именно из-за его действий окно меняет свои состояния. А нам, в свою очередь, необходимо на это реагировать для корректной работы приложения. Решение напрашивается само собой — необходимо делегировать нужные нам колбэки на VM.
Представим, что нужно загружать информацию каждый раз, когда пользователь возвращается на активность. Для этого нам нужно вызывать метод загрузки данных в onResume( ).
Изменим ProfileActivity:
И определим этот же метод в ProfileViewModel:
Теперь данные будут обновляться каждый раз, когда пользователь будет возвращаться на окно. К тому же, если информация еще не была получена до этого — покажется соответствующее состояние. Все очень просто 🙂
В точности делаем и с остальными нужными нам методами. Естественно, определять это каждый раз при создании VM нецелесообразно, поэтому вынесем эту логику в базовые классы. Назовем их BindingActivity и ActivityViewModel:
Теперь по аналогии со стандартным поведением Activity для того, чтобы реагировать на интересующие нас изменения, нам нужно просто переопределить нужный метод.
Как по мне, так нет никакой необходимости каждый раз при создании активности описывать создание биндинга и подвязку к нему VM. Эту логику также можно вынести в базовый класс, но немного изменив привычный нам метод onCreate() . Адаптируем его для получения VM при создании активности и добавим еще пару абстрактных методов для необходимых параметров:
Осталось еще сделать базовый класс для ActivityViewModel. Здесь все проще — добавим в него только экземпляр Activity. Он пригодится нам для создания интентов а также подойдет в качестве контекста:
На этом с активностями всё — у нас есть все необходимые инструменты для описания логики, за исключением одной неприятной мелочи. Такие поля как “viewModel” и “binding” в базовой активности явно типизированы, что усложнит работу с ними, вынуждая каждый раз приводить типы. Поэтому обобщим наши классы следующим образом:
Готово! В конечном итоге мы получили такой вот класс активности:
getVariable() — должен возвращать название переменной, которое указано в тэге data->variable xml-файла активности, а getLayoutId() — должен вернуть тот самый xml. При этом также стоить отметить, что ProfileViewModel должен наследовать ActivityViewModel.
В реализации подобных классов для фрагментов есть небольшие отличия, но подробно рассматривать реализацию в рамках статьи мы не будем, т.к. концепция у них всё же схожа. Готовый класс можно будет увидеть ниже.
Несколько полезных примеров
С момента нашей последней статьи о DataBinding библиотека не только потеряла статус “бета”, но и обросла некоторыми очень полезными нововведениями. Одним из таких является двустороннее связывание (two-way binding). Это когда не только данные влияют на UI, но и наоборот. К примеру, когда пользователь вводит свое имя в EditText, значение сразу обновляется и в переменной. Ранее мы уже делали подобную фичу, однако для этого привлекались TextWatcher и BindingAdapter. Теперь же достичь этого можно намного проще (я бы даже сказал проще некуда). Все, что для этого нужно — изменить
android:text=»@
Мы добавили здесь ViewModel для view в диалоге, т.к. передача ObservableField напрямую в variable не работает корректно (не знаю, баг это или фича, но то, что это не очевидно — факт). Точно также можно привязываться и к другим атрибутам, таким как checked у CheckBox и RadioButton, enabled и пр.
Если вам нужно как-то реагировать или менять данные во время их ввода/вывода — вы можете переопределить методы get() и/или set() у Observable-поля и там производить нужные манипуляции.
А если задача стоит только в отслеживании изменений — можно добавить OnPropertyChangedCallback:
Еще одна фича — это возможность использовать сеттеры как атрибуты в разметке. Допустим, у нас есть метод setAdapter() у того же RecyclerView. Для того, чтобы установить его, нам нужно обращаться непосредственно к экземпляру виджета и вызывать его метод прямо из кода, что противоречит нашему подходу. Для решения этой проблемы можно создавать BidningAdapter, или вообще CustomView, который будет расширять RecyclerView и в нем добавлять свои атрибуты. Но, согласитесь, это не лучший вариант.
К счастью, все намного проще — благодаря кодогенерации мы можем указывать в xml название сеттера при этом просто опуская “set”. Таким образом, задать адаптер можно вот так:
Префикс “bind” — этот все тот же “appliaction namespace”, и, если он уже объявлен, их лучше попросту дублировать для того, чтобы не путать объявленные кастомные атрибуты с атрибутами, сгенерированными с помощью биндинга:
Тем не менее, идея с CustomView имеет право на жизнь в случае, если нужного сеттера в виджете нет (или он назван по неподходящему нам формату).
Возможно кто-то из вас уже задался вопросом о том, как в при такой архитектуре передавать параметры во ViewModel? Здесь все также применяется подход с делегированием, но для удобства мы создаем статический метод open (или openForResult) в котором перечисляем все необходимые параметры. Дальше достаем их и передаем во ViewModel, в котором есть соответствующий конструктор. Например, передадим нашей активности статус как параметр:
Еще одна небольшая наработка, которой хотелось бы поделиться — это вынесение полей “isLoading” и “isError” в базовый класс ViewModel. Эти поля публичные и относятся к типу ObservabeBoolean — благодаря этому нет необходимости дублировать логику состояний загрузки и ошибки, а реагировать на их изменение можно простым include:
При необходимости можно вынести сообщения и иконки для разных кейсов (например, разные причины ошибки) в отдельные variable и таким образом получить гибкий компонент, который применяется парой строк в любом лэйауте.
Забудем о boilerplate!
За время использования MVVM в разработке мы столкнулись с тем, что нам приходилось писать много надоедливого кода: модификация Activtiy/Fragment под базовые классы, прописывание длинных названий Binding-классов в дженериках, создание и связывание ViewModel, а на ранних стадиях и вовсе приходилось копировать базовые классы из проекта в проект, что также занимало драгоценное время. Именно поэтому мы создали библиотеку и плагин для Android Studio, с помощью которых эта рутина стала занимать всего 2-3 клика.
Библиотека AndroidMvvmHelper — это набор базовых классов для удобной работы с MVVM. В этот перечень входят классы как для работы с Activity (BindingActivity и ActivityViewModel), так и с Fragment (BindingFragment и FragmentViewModel), в которых уже реализована логика связывания, а также определены необходимые методы для получения callback-ов. Для того, чтобы начать ее использовать — необходимо просто определить зависимость в grdale-файле:
Хотя решение с библиотекой и упрощает жизнь, создание классов все еще достаточно трудоемкий процесс. Для решения этой задачи мы разработали плагин для IntelliJ IDEA и Android Studio — MVVM Generator. Он позволяет в один клик создать класс BindingActivity (или BindingFragment), его ViewModel, подготовленный xml-файл для разметки и зарегистрировать компонент в AndroidManifest (в случае активности, конечно же). Кроме того, если плагин не обнаружит зависимости библиотеки MVVMHelper, она будет автоматически добавлена.
Чтобы его установить, нужно зайти в раздел управления плагинами:
В нем нажать кнопку “Browse repositories” для поиска доступных плагинов в сети:
В поле поиска вводим “MVVM Generator”, выбираем найденный плагин и жмем “Install”:
По окончанию установки необходимо перезапустить IDE. После этого плагин готов к работе.
Теперь давайте создадим фрагмент профиля. В случае, как и при создании обычного класса, вызываем контекстное меню на нужном пакете и выбираем “Create Binding Fragment”
После того, как мы введем название фрагмента (в нашем случае это “ProfileFragment”) мы получим следующее:
Заглянув внутрь, мы увидим готовые к работе классы:
Помимо этого у нас сразу готов xml:
И все это буквально за пару секунд! Итак.
Резюмируя
На сегодняшний день плагин очень простой и решает только главную задачу — генерацию файлов. В планах присутствуют добавление проверки на наличие биндинга, более гибкая валидация названий, конфигурация расширенных шаблонов активностей и фрагментов и много другое, но пока — имеем то, что имеем.
С момента выхода стабильной версии DataBinding наша команда уже успела реализовать несколько проектов с помощью этого подхода. Из собственного опыта могу лишь сказать, что мне не хочется возвращаться к более традиционным способам написания приложений, а когда все же приходится это делать — чувствуешь себя человеком из будущего. В целом рутинной работы стало меньше, а потому и процесс разработки стал интересней. К тому же, ребята из Google активно работают над адекватной поддержкой этой технологии в Android Studio, что значительно минимизирует неудобства при разработке. Теперь же — это основной подход, который используется нами при создании приложений.
Надеемся, что наш опыт поможет упростить жизнь при создании MVVM-архитектуры в Ваших приложениях так же, как это помогает нам 🙂
Источник