Android studio android junit test

Testing on Android using JUnit 5

Published on 30 Nov 2018 · 8 minute read

JUnit 4 has always been the standard unit testing framework for Android development, however, a production version of JUnit 5 has been available for quite a while. JUnit 5 is the next generation of JUnit testing and has quite a new structure. From a user-perspective it offers a number of useful new features and various other benefits. Often Android developers are used to waiting a while for new things from the Java-world to be an option on Android. In the case of JUnit 5, with some changes to your Gradle dependencies, you can use it right now from your Java and Kotlin unit tests.

Testing on Android with JUnit 5

The architecture of JUnit 5 is quite a significant change from before, with the framework being split into three major components.

  • JUnit Platform is the underlying layer on which testing frameworks can run on the JVM and offers up the API for different test engines.
  • JUnit Jupiter defines how we write JUnit 5 tests and then contains an engine for running these tests on the platform.
  • JUnit Vintage gives us an engine for running our previous JUnit 4 tests. We don’t need to worry about having to update all of our tests in one go, as we can use the vintage engine to run these and have both JUnit 4 and 5 tests within our project.

As users of the testing framework, it’s likely that the most interesting part is the new features we get access to. Nested tests allow us to put our tests into groups, increasing readability and allowing us to reduce repetition within test names. Parameterised tests are really powerful and can also reduce duplicating tests to give different inputs, the parameters can even be provided through a variety of mechanisms. Dynamic tests offer an API to generate tests on the fly using a test factory, rather than hard-coding each of the tests.

Parameterised tests run tests with different inputs

The JUnit 5 extension model allows us to extend the test framework and is the evolution of concepts from JUnit 4 such as @Rule and custom runners. The extension model will allow JUnit 5 to evolve with all developers and tool makers having the same public extension API. There are many more features beyond those listed here, all of which can be found in the user guide.

Now, let’s get JUnit 5 running in our Android project. 🛠

Configuring Gradle

Thanks to Marcel Schnelle (mannodermaus) there is an easy-to-use Gradle plugin that will configure all the testing tasks to use JUnit 5. The plugin has a few minimum requirements that our project needs to meet, at the time of writing this is the Android Gradle plugin 3.2.0+, Gradle 4.7+ and Java 8+.

We will start by adding the plugin to our root build.gradle file using the latest version, which at time of writing is 1.3.2.0.

→ /build.gradle

After a successful Gradle sync, we will be ready to apply the plugin to any Android modules we wish to use JUnit 5 in.

→ /app/build.gradle

If there are JUnit 4 tests in the project we are configuring, then we will also need to keep the JUnit 4 dependency and enable the JUnit vintage test engine mentioned above.

Once the plugin and the Gradle dependencies have been configured, we can run tests in the same way as we were previously using the same Gradle tasks and run configurations in Android Studio.

If we need them, there are some options for configuring the test environment within Gradle. Our settings go in a junitPlatform block within the existing testOptions , where we may already be doing some configuration. The most common use-case would be for filtering which tests are executed. We could use the filters to divide our tests into unit and integration and then have them run for different variants, as an example.

For any more information on the plugin, all the setup and usage instructions can be found on GitHub.

JVM tests only

If our unit tests are only going to be ran on the JVM, no Robolectric or Android required, then we can get away without the Gradle plugin. To do this we can remove the plugin classpath entry from the root build.gradle and the line where the plugin is applied within the app module build.gradle . Instead we need to add some configuration to testOptions to enable running with JUnit 5.

Читайте также:  Как китайский андроид сделать русским

→ /app/build.gradle

We lose the per-variant filters that were demonstrated above if we go down this route. However, for most Android projects that aren’t using Robolectric this should definitely be sufficient. 👍

Writing the tests

Let’s write some tests to explore the new features of JUnit 5 and how we should go about using it in our apps. We will be using AssertJ for our assertions, mainly for readability within the samples. We will be testing a very simple repository that allows us to look up contacts using their IDs, starting with the JUnit 4 version.

Converting this test to JUnit 5 is as simple as changing the import for @Test . Projects with many more tests, using more features of JUnit 4, may have more changes that are required. As mentioned above, we can start new tests in JUnit 5 and then either leave the JUnit 4 ones as is, or convert them gradually.

Descriptive test names

A common structure for tests is breaking them into given, when and then.

  • The given clause involves setting up the conditions for the test, such as mocking or configuring the component we are about to test.
  • Within the when clause, we execute the behaviour we are looking to verify.
  • It is in the then clause that we check the behaviour is working correctly with any assertions.

This structure is fairly clear within the body of the test function, however, in the naming of the function some issues may start to arise.

Some approaches could be taken to reduce the length, such as using shorter wording in the given clauses and potentially removing the then clause from the name. This does result in losing some detail, which can be really useful in test names to quickly see in the test report what is going on and to demonstrate what a test is supposed to do. It is also much easier to read a descriptive sentence than the camel-case seen in function names.

JUnit 5 introduces the @DisplayName annotation, which can be used to provide a descriptive name for the test report. The same result can be achieved in Kotlin through the use of back-ticks around the function name. The advantage of the @DisplayName approach is that we keep the searchable and familiar function name, whereas the back-tick approach avoids the need to maintain both the function name and annotation.

Structuring tests

A common reason for test names growing in length is having multiple tests with the same starting condition. Beyond naming, a similar starting condition can cause test bodies to grow in length as well! To solve the issue, JUnit 5 introduces @Nested , allowing you to group a set of tests into an inner class. The group can have a shared starting state and can also have a display name specified, reducing the length of individual test function names. The nested structure is shown within the test report, making it very readable.

After fleshing out the tests for this repository more, with a few tests in each group, we can peek at the test report to see how it all looks. 👀

Android Studio test report

The code we have written is available on GitHub.

Instrumentation tests

We have been discussing unit tests only and as we all know instrumentation tests, usually using Espresso, are also very important. Currently, our Espresso tests will be using JUnit 4, which can be left as it was previously. This means using the JUnit 4 test runner, as opposed to adding the JUnit 5 vintage engine and using the new infrastructure.

Instrumentation tests using the JUnit 5 Gradle plugin are still considered experimental

The Gradle plugin we used to introduce JUnit 5 on Android has support for instrumentation tests, which at the time of writing is considered experimental. From checking the GitHub issues page for the plugin, many people have had problems using JUnit 5 for their instrumentation test suite. When the situation changes this article will be updated with the current details of using it.

Читайте также:  Секретные функции андроид самсунг

Since JUnit 5 is built upon Java 8, to use it we need a minSdkVersion of 26 or above. Before we start freaking out, this can be achieved by running the tests with a product flavour that has the minimum SDK increased to this level. 😅

To write and run our instrumentation tests using the JUnit 5 framework, we will need some Gradle configuration. A little bit more is required than for unit tests, due to needing to tell the system to to use the plugin’s runner builder and ensuring tests running on the device work correctly.

→ /app/build.gradle

It is noteworthy that instrumentation tests tend to have less need for JUnit 5, due to there being less tests and fewer variants of similar tests, in general. This will of course depend heavily from project to project though! To find out more, please check out the plugin’s GitHub page.

Wrap up

We have only considered a few small aspects of writing JUnit 5 tests, as there is simply so much to look at. To find out more, there is a detailed user guide covering all parts of the framework and how to go about writing tests and customising the test process.

By using JUnit 5 in our Android app testing we are using the latest evolution of the JUnit framework. It gives us access to more features, helps us make tests as readable as possible, reduce duplication within our tests and extending the test framework has never been easier. Hopefully it will catch on more in the Android community and become the “standard” for Android development sooner rather than later. 🚀

Are you using JUnit 5 in your Android project, or did you have any issues getting it setup? What ways have you seen it improve your testing process? Please feel open to reaching out to me on Twitter @lordcodes with any questions or thoughts you have, or about anything else.

If you like what you have read, please don’t hesitate to share the article and subscribe to my feed if you are interested.

Thanks for reading and happy coding! 🙏

Last updated on 19 Feb 2020

Like what you read? Please spread the word.

If you would like to share it, it would be very much appreciated.

Hi, I hope you enjoyed the article. I am Andrew — a builder of Android and iOS apps, games and developer tools. Articles on this blog focus on all aspects of Android and iOS development using Kotlin and Swift. When it comes to coding, my particular focus is on code quality and automated testing. Aside from development I am an avid gamer and music fan.

Источник

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

Настройка

Если вы почему-то не используете 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, рекомендую почитать официальную документацию, а вот материал про возможности инструментальных тестов.

Источник

Оцените статью