- 10 ноября 2016 г. Простые Unit-тесты в Android
- Общие сведения
- Настройка
- Local-тесты в Android
- Instrumented тесты в Android
- Заключение
- Getting Android’s context for unit testing #134
- Comments
- jmiecz commented May 30, 2018
- arnaudgiuliani commented May 31, 2018
- jmiecz commented May 31, 2018
- arnaudgiuliani commented Jun 1, 2018
- jmiecz commented Jun 1, 2018 •
- arnaudgiuliani commented Jun 4, 2018
- qbait commented Aug 16, 2018 •
- qbait commented Aug 17, 2018
- arnaudgiuliani commented Aug 20, 2018
- qbait commented Aug 20, 2018
- arnaudgiuliani commented Aug 20, 2018
- NurseyitTursunkulov commented May 19, 2019
- jasofalcon commented May 16, 2021
- Лекция 6 по архитектуре Android. Unit тестирование. Test Driven Development
- Введение
- Unit-тестирование
- JUnit
10 ноября 2016 г. Простые Unit-тесты в Android
Вот и настало время разобраться и написать небольшую заметку о том, что из себя представляет тестирование логики Android-приложений. К этому вопросу я пришел не сразу, однако учиться никогда не поздно!
Общие сведения
Для начала определимся, что такое тесты и зачем их нужно писать. Из Wikipedia:
Модульное тестирование, или юнит-тестирование (англ. unit testing) — процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы.
Идея состоит в том, чтобы писать тесты для каждой нетривиальной функции или метода. Это позволяет достаточно быстро проверить, не привело ли очередное изменение кода к регрессии, то есть к появлению ошибок в уже оттестированных местах программы, а также облегчает обнаружение и устранение таких ошибок.
Другими словами, тесты — это методы, которые проверяют работоспособность нашего кода. Но каким образом это делается? Давайте по порядку. Тест считается выполненным, если он выполнился без ошибок. А для разного рода проверок используются вспомогательные методы вроде «семейства» методов assertXXX (см. примеры ниже).
Unit-тестирование в Android можно разделить на 2 типа:
- Локальные unit-тесты (Local unit tests) — тесты, для выполнения которых используется только JVM. Они предназначены для тестирования бизнес-логики, которая не взаимодействует с операционной системой.
- Инструментальные unit-тесты (Instrumented unit tests) — тесты, с помощью которых тестируется логика, «завязанная» на Android API. Их выполнение происходит на физическом девайсе/эмуляторе, что занимает значительно больше времени, чем локальные тесты.
Выбор одного из этих 2 типов зависит от целей тестируемой логики. Естественно, если это возможно, лучше писать локальные тесты.
Также при создании тестов стоит уделить внимание организации пакетов. Существует следующая структура, которой стоит придерживаться:
- app/src/main/java — исходный код приложения;
- app/src/test/java — здесь размещаются локальные тесты;
- app/src/androidTest/java — сюда помещаем инструментальные тесты.
Настройка
Если вы почему-то не используете Android Studio, которая генерирует пакеты при создании проектов, стоит запомнить структуру выше. К тому же, IDE конфигурирует Gradle-файл модуля app:
Для упрощения написания тестов огромной популярностью пользуется фреймворк Mockito. В его возможности входит имитация и наблюдение объектов, а также вспомогательные средства проверок. Чтобы начать его использовать, добавим зависимость:
Local-тесты в Android
Теперь давайте рассмотрим 2 простейших теста, которые проверяют на соответствия 2 объекта (в нашем случае — числа):
Для этого создадим в пакете для локальных тестов класс и разместим в нем наши методы. Чтобы JUnit знал, что эти методы являются тестами, они помечаются соответствующей аннотацией @Test . Метод же assertEquals() кидает ошибку AssertionError в случае, если первый (ожидаемый) и второй (результат) не соответствуют друг другу.
Давайте запустим их (нажав правой кнопкой на класс и выбрав Run ‘ExampleUnitTest’ в появившемся меню) и посмотрим на результат выполнения:
Видно, что первый метод выполнился, а во втором произошла ошибка, т.к. 5 != (2 + 2). Также можно настроить тест на ожидаемое исключение используя параметр expected :
В таком случае тест выполнится, т.к. это исключение мы ожидали. Для длинных операций можно также указать параметр timeout и установить значение в миллисекундах. Если метод не выполнится в течение заданного времени, тест будет считаться проваленным:
Есть еще такой хитрый механизм как Matchers. К примеру, их на вход принимает метод assertThat(T actual, Matcher matcher) и возвращают методы класса org.hamcrest.CoreMatchers . Матчеры представляют собой логические операции совпадения. Рассмотрим несколько из них:
Как видно из названий, is() можно описать как оператор «равно», is(not()) как «неравно», а hasItem() — как проверку на наличие элемента в списке. И читается это как связное предложение. Здесь можно найти весь перечень матчеров.
Пока мы видели несложные примеры, на основании которых уже можно писать простые тесты. Но я бы хотел рассмотреть библиотеку Mockito, с чьей помощью наши тесты станут по-настоящему крутыми!
Как упоминалось выше, Mockito используется для создания «заглушек». Они называются mock-объектами. Их цель — заменить собой сложные объекты, которые не нужно/невозможно тестировать. Объявить их можно двумя способами:
Обратите внимание, что для того, чтобы использовать аннотацию @Mock , класс нужно пометить аннотацией @RunWith(MockitoJUnitRunner.class) или вызвать MockitoAnnotations.initMocks(this); в @Before -методе:
Получив «замоканный» объект, можно творить настоящие чудеса! К примеру, для того, чтобы «переопределить» название приложения из строкового ресурса, можно сделать следующее:
Теперь при вызове метода getString() будет возвращаться «Fake string», даже если он вызван неявно (за пределами теста):
Но что если мы хотим переопределить все строковые ресурсы? Неужели нам придется прописывать эту конструкцию для каждого из них? Естественно, нет. Для этого предусмотрены методы anyXXX() , (anyInt() , anyString() , etc. Теперь, если заменить R.string.app_name на anyInt() (не забываем, что в Android все ресурсы имеют тип Integer) — все строки будут заменены на нашу строку.
А убедиться в этом мы можем, дописав assertThat() в конце нашего теста:
В случае, когда мы хотим выбросить Exception, можно использовать конструкцию when(. ).thenThrow(. ); .
Есть еще магический метод verify() который провалит тест в случае, если указанный метод не был до этого вызван. Взглянем на код:
Работает это так: мы передаем в verify(mockedList) наш мок-лист, после чего указываем, какие именно методы нас интересуют. В данном случае добавление строки и очистка списка. Неотъемлемый инструмент, как по мне 🙂
Но по-настоящему потенциал этого метода раскрывается при использовании spy-объектов. Их главное отличие в том, что они не создаются напрямую, в отличие от mock’ов. А толку-то с них, спросите вы? А толк в том, что создав «шпиона» вы можете следить за ним также, как и за фейковым объектом:
Instrumented тесты в Android
При помощи этого типа тестов мы получаем доступ к реальному контексту, а также ко всем возможностям Android API. Помимо этого, у нас есть «режим Бога», при помощи которого мы можем управлять жизненным циклом активности. Для того, чтобы тест распознавался как инструментальный, нам нужно пометить класс соответствующей аннотацией:
Сами же тесты мы помечаем так же, как и в случае локальных — при помощи аннотации @Test . Давайте проверим, соответствует ли пакет нашего контекста нашему приложению:
С помощью имеющегося инструментария можно прослушивать состояние активностей:
. или и вовсе управлять их состоянием:
Заключение
Это, пожалуй, самые важные вещи, которые мне удалось найти при первом знакомстве. Поэтому не закидывайте камнями, как говорится 🙂 Но, я думаю, для начала этого будет достаточно. Для тех, кто хочет узнать больше о возможностях junit, рекомендую почитать официальную документацию, а вот материал про возможности инструментальных тестов.
Источник
Getting Android’s context for unit testing #134
Comments
jmiecz commented May 30, 2018
Is there a way to get either the Android’s context or resources to use for a unit test? The use case is, having a json file we can reference vs hard coding the json string into the class we are unit testing.
The text was updated successfully, but these errors were encountered:
arnaudgiuliani commented May 31, 2018
with Junit and Mockito we can’t recreate a «real» Android context. You can use more easily make such thing in Android instrumented tests.
I can be interesting to investigate with new nitrogen project, or more about Robolectric project if we can do something.
jmiecz commented May 31, 2018
If I were to use Robolectric, I know they offer a way to grab the context, how can I pass it to one of my modules?
arnaudgiuliani commented Jun 1, 2018
If you want to add/override the existing definition of Application , you just have to provide a module like:
And then use the androidContextModule in the list of modules that you use, at the end of the list of modules: startKoin(listOf(module1, . androidContextModule))
Do we have a direct function to create an Android context with Robotlectric?
jmiecz commented Jun 1, 2018 •
Hello @arnaudgiuliani, Below is a sample unit test using what you suggested and having success on accessing the resources! Please let me know if there is anything else I can do to help.
arnaudgiuliani commented Jun 4, 2018
Great 👍
A sample/geetting started project will gather test samples for Android. I keep you in touch!
qbait commented Aug 16, 2018 •
Unfortunately, I still have a problem with configuring robolectric and koin. I’m getting the following error
I’ve also copy and paste @jmiecz code and tried on @arnaudgiuliani koin’s sample app with the same results.
qbait commented Aug 17, 2018
In the end it works. I use version 1.0.0-beta-6
I don’t have any @Before and @After nor specific modules for tests. Is my configuration alright or it works by accident? 🙂
arnaudgiuliani commented Aug 20, 2018
Nop. For an instrumented test, your Koin container is already started via your startKoin() .
qbait commented Aug 20, 2018
What do you mean by instrumented tests @arnaudgiuliani ?
My tests are under test folder and run on jvm. Are they still called instrumented because of Robolectric
arnaudgiuliani commented Aug 20, 2018
Yes kindof. This is unit test, with Android instrumented classes.
NurseyitTursunkulov commented May 19, 2019
If you want to add/override the existing definition of Application , you just have to provide a module like:
And then use the androidContextModule in the list of modules that you use, at the end of the list of modules: startKoin(listOf(module1, . androidContextModule))
Do we have a direct function to create an Android context with Robotlectric?
where to put it?
jasofalcon commented May 16, 2021
applicationContext < >doesnt seem to exist anymore, is there a working github example with latest koin?
Источник
Лекция 6 по архитектуре Android. Unit тестирование. Test Driven Development
Шестая лекция курса по архитектуре клиент-серверных android-приложений, в которой мы поговорим о Unit-тестировании. Узнаем, как тестировать андроид-приложения, как работать с библиотекой Mokito, как измерить уровень покрытия тестами в вашем проекте. Отдельно рассмотрим такой подход к рахзработке, как Test Driven Development.
Введение
Мы рассмотрели способы решения большинства задач, которые были поставлены в начале курса: мы знаем различные способы организации клиент-серверного взаимодействия, преимущества и недостатки каждого способа, мы умеем строить грамотную и модульную архитектуру приложения, в рамках которой становится удобно читать существующий код и писать новый. Поэтому нам осталось разобрать еще один вопрос – как именно нужно писать тесты. Пока что мы только утверждали, что такая архитектура позволяет писать тесты, но это утверждение не было подкреплено реальными примерами. И сейчас настал момент исправить это упущение.
На сегодняшний день тестирование систем является неотъемлемой частью процесса их разработки, и, разумеется, приложения под Android не стали исключением. Тестирование систем позволяет приобрести определенную уверенность в корректности и качестве их работы. Кроме того, тестирование позволяет проще вносить изменения (после внесения изменений систему можно протестировать и убедиться, что она работает корректно).
Существует немало видов тестирования, но для начала нас интересует разделение тестирования по степени автоматизации. Здесь можно очень грубо разделить тестирование на автоматизированное и ручное. Ручное тестирование подразумевает проверку системы человеком, сверку с техническим заданием и требованиями и поиск несоответствий и багов. Автоматическое тестирование позволяет выполнять тестирование системы программно.
Разумеется, у обоих этих способов есть свои преимущества и свои недостатки. Ручное тестирование позволяет лучше выявить недостатки, в том числе и такие проблемы, которые сложно найти с помощью автоматизированного тестирования (к примеру, с помощью автоматизированного тестирования будет тяжело заметить неправильный шрифт или неправильное расположение элементов).
С другой стороны, ручное тестирование в тысячи раз медленнее автоматического. В среднем, за рабочий день тестировщик может полностью проверить работу достаточно небольшого приложения за один день на одном устройстве, в то время как автоматическое тестирование позволяет буквально за 10 минут проверить корректность работы приложения на большом количестве устройств (или мгновенно выполнить тесты для бизнес-логики на JUnit).
Поскольку каждый вид тестирования имеет свои преимущества, правильным решением, будет их совместное использование.
Но нас, разумеется, будет интересовать только автоматическое тестирование, которое позволяет разработчику самостоятельно протестировать свое приложение. Кроме того, написание тестов при разработке имеет еще несколько очень важных преимуществ:
- Если вы хотите писать тесты, то вам придется придерживаться определенного архитектурного стиля. Мы обсуждали это уже не раз, и именно возможность тестирования была одной из основных причин, из-за которой мы занимались изучением архитектуры приложений.
- Написание тестов позволяет еще на этапе разработки выявить некоторые проблемы и случайные ошибки в вашем коде.
- Если в вашем проекте есть автоматические тесты, вы можете с меньшим риском вносить изменения в различные участки кода, поскольку с помощью тестов вы можете быстро проверить, что новыми изменениями вы не сломали старое поведение (конечно, тесты дают не гарантию корректности кода, а только лишь некоторую уверенность в этом, что тоже хорошо).
- Наличие тестов позволяет контролировать процесс разработки: тесты можно поставить на CI-сервер, чтобы отслеживать текущее состояние кода или проверять рабочие ветки, что очень удобно и дает гарантию того, что в продуктовую ветку попадет только код с выполняющимися тестами.
- Автоматические тесты могут служить документацией к вашему коду.
Кроме того, существует немало других разделений по видам тестирования. Один из наиболее употребляемых и важных разделений тестирования – это разделение тестирования по степени их модульности. Здесь в общем случае выделяется 3 вида тестирования:
- Модульное тестирование – это тестирование отдельных модулей системы в независимом от других модулей окружении. Это, вероятно, наиболее популярный и известный вид тестирования, и это логично. Чем более детальное тестирование мы выполняем, тем легче найти потенциальные ошибки, так как в тестировании одного модуля мы проверяем корректность работы только этого модуля, а он, конечно, имеет намного меньшую сложность, чем вся система в целом. Именно для реализации модульного тестирования мы и создавали гибкую и удобную архитектуру с разбиением приложения на слои и отдельные модули.
- Интеграционное тестирование – после проверки корректности работы отдельных модулей нужно проверить, как эти модули взаимодействуют между собой. Потому что система – это не только набор отдельных модулей, но и правила и средства их взаимодействия. Каждый модуль в отдельности может работать правильно, но при этом объединение нескольких модулей может содержать ошибки. Это тестирование выполняется уже на более высоком уровне.
- Системное тестирование – после объединения всех модулей приложения в единую систему и проведения интеграционного тестирования для различных групп модулей нужно провести тестирование того, как система работает в целом и насколько она соответствует изначальным требованиям. Это тестирование на глобальном уровне, поэтому детали реализации отдельных модулей уже не играют роли, – система проверяется больше с точки зрения пользователя, или по методу черного ящика.
В рамках системы Android можно выполнять автоматизированное тестирование всех видов, изложенных выше. Но есть и особенности. Во-первых, в рамках нашей архитектуры основным компонентом, который содержит бизнес-логику и который должен быть протестирован в первую очередь, является делегат или Presenter. Presenter – это обычный Java-класс (с возможными зависимостями от Android), поэтому тесты для него пишутся в рамках модульного тестирования и с помощью стандартного фреймворка JUnit. Также в рамках модульного тестирования нужно протестировать работу слоя данных.
В случае модульного тестирования все достаточно очевидно. Но что же насчет интеграционного и системного тестирования? Что является интеграцией нескольких модулей в случае Android-приложения? Обычно под интеграцией нескольких модулей понимается конкретный экран приложения, на котором объединяются модули, содержащие бизнес-логику, и модули из слоя данных. Конкретный экран уже должен быть протестирован с точки зрения пользователя, хоть и с небольшими знаниями о том, как устроена система внутри.
И насчет системного тестирования также все очевидно – это тестирование всего приложения и всех его экранов. Оно также выполняется с точки зрения пользователя.
Тестирование слоя данных, а также интеграционное и системное тестирование в Android реализуются с помощью специальных средств, которые будут рассмотрены в рамках следующей лекции. А сейчас мы перейдем к тестированию бизнес-логики приложения, а именно к тестированию Presenter-ов.
Unit-тестирование
JUnit
Мы много раз сказали о тестировании, а также о разных средствах для написания тестов. Но что же такое тесты? Интуитивно мы понимаем, что это означает. Тесты – это некоторые проверки или утверждения, которые позволяют в определенной степени убедиться в корректности работы системы.
Для модульного тестирования Java-кода используется фреймворк JUnit. Этот фреймворк позволяет конфигурировать окружение для тестов, исполнять код, который будет проверять работу некоторых классов, и выводить результаты тестов (сколько успешных тестов, сколько ошибок, и где именно произошли ошибки).
Разберем простейший пример написания тестов. Допустим, у нас есть класс, в котором определен только один метод для сложения двух чисел:
Источник