To main content
 
Виктор Мацкевич

Тестирование при помощи библиотеки JUnit для платформы Android

Основной подход к разработке тестов

Рассмотрим применение библиотеки JUnit для тестирования Андроид приложения. В качестве тестируемого приложения был написан простой калькулятор (activity_main.xml и MainActivity.java).

Кратко опишем алгоритм работы приложения. Вводим в поле первое число, после чего выбираем тип операции (сложение, умножение, деление или вычитание). После этого введенное значение сохраняется, а поле ввода затирается. Вводим в него второе число, после чего жмём кнопку «Calculate». В текстовом поле выводится результат. Пример представлен на изображении ниже.
В классе MainActivity. java представлена вся логика работы приложения. Стоит помнить, что для того, чтобы легче было покрыть весь функционал приложения тестами, необходимо разбить функционал на методы.

Также стоит обратить внимание на аннотацию к @VisibleForTesting, она указана как у экземпляров классов, так и у методов. Данная аннотация позволяет обращаться к объектам или методам в тестах. Основной особенностью является то, что если обращаться к этим объектам не из тестов, то по умолчанию параметром доступа будет является «private». Также можно указать режим доступа вручную. Например:
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
view raw 1.java hosted with ❤ by GitHub
Перейдём непосредственно к самому тесту. Сперва зайдем в build.gradle и вставим следующие зависимости:
apply plugin: 'com.android.application'
android {
...
}
dependencies {
...
androidTestCompile 'com.android.support:support-annotations:25.3.1'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
}
view raw 2.java hosted with ❤ by GitHub
Синхронизируем проект, тем самым подтянув добавленные нами зависимости.

После этого заходим в наш класс, который мы хотим протестировать, и выбираем "Create Test".
Появится диалоговое окно:
Выберем библиотеку для тестирования JUnit 4. Ставим галочки напротив setUp и tearDown. Данные методы являются методами жизненного цикла теста. После чего жмём кнопку «OK». Здесь мы выбираем директорию с наименованием «androidTest».

Более подробнее об этих директориях можно прочесть на этой странице.
В результате мы получили новый класс MainActivityTest.java. Собственно здесь и будут располагаться наши тесты.

Но на этом ещё не всё. Нам также необходимо выполнить ряд действий:
  1. Создать объект класса ActivityTestRule, а также добавить аннотацию @Rule к этому объекту. Данный объект является указателем на объект тестирования.
  2. Реализуем методы setUp() и tearDown(). В методе setUp() мы получаем activity, а в методе tearDown() мы присваиваем null данному объекту.
  3. Наследуем наш класс от класса Assert. В классе Assert представлен ряд методов для сравнений объектов. Данный класс значительно упростит работу с тестами
Аннотация @Before — метод, обозначенный данной аннотацией, вызывается перед началом выполнения теста. Обычно в теле метода, обозначенного данной аннотацией, выполняются все подготовительные операции. Например, загрузка валидных данных и т. д.
Аннотация @After вызывается непосредственно после завершения теста.
Первый тест
Напишем первый тест. Нашей задачей является проверить корректность работы метода cleanData() класса MainActivity.java.

Для этого добавим следующий фрагмент кода в класс MainActivityTest.java.
@Test
public void testCleanData_input2plus2_allObjectsCleaned() {
assertNotNull(mMainActivity);
final Button addButton = mMainActivity.getAddButton();
final EditText inputDataEditText = mMainActivity.getInputDataEditText();
mMainActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
inputDataEditText.setText(String.valueOf(2));
addButton.performClick();
inputDataEditText.setText(String.valueOf(2));
}
});
getInstrumentation().waitForIdleSync();
assertEquals(inputDataEditText.getText().toString(), "2");
assertEquals(mMainActivity.getTextResult(), "2.0 +");
assertTrue(mMainActivity.mFirstValue == 2);
assertTrue(mMainActivity.mSecondValue == 0);
final Button cleanButton = mMainActivity.getCleanButton();
mMainActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
cleanButton.performClick();
}
});
getInstrumentation().waitForIdleSync();
assertEquals(mMainActivity.getInputDataEditText().getText().toString(), "");
assertEquals(mMainActivity.getTextResult(), "");
assertTrue(mMainActivity.mFirstValue == 0);
assertTrue(mMainActivity.mSecondValue == 0);
}
view raw 3.java hosted with ❤ by GitHub
Название теста достаточно субъективно. В данной статье предложен вариант оформлять название теста следующем образом:
[Name function]_[Keys]_[Expected result]
где [Name function] — название основной функции для тестирования,
[Keys] — значения, подаваемые на вход тесту. Если объектов много, то не стоит перечислять все, а попробовать логически сгруппировать.
[Expected result] — ожидаемый результат

В нашем случае можно прочесть название теста следующим образом: тест функции cleanData() вводим «2». после жмём «+» и вновь вводим двойку, в результате выполнения функции cleanData() все объекты будут приведены в исходное состояние.

Теперь рассмотрим последовательно фрагменты кода теста и распишем их более детально.

Функция assertNotNull(Object) проверяет объект на null. Советую не пренебрегать данным методом и использовать его перед вызовом функций у объекта. Типичная проверка объекта.

Чуть ранее было сказано про аннотацию @VisibleForTesting. Давайте добавим в класс MainActivity. java следующие методы c применением данной аннотации:
@VisibleForTesting
public String getTextInputData() {
return mInputDateEditText.getText().toString();
}
@VisibleForTesting
public String getTextResult() {
return mResultTextView.getText().toString();
}
@VisibleForTesting
public Button getAddButton() {
return mAddButton;
}
@VisibleForTesting
public EditText getInputDataEditText() {
return mInputDateEditText;
}
@VisibleForTesting
public Button getCleanButton() {
return mCleanButton;
}
@VisibleForTesting
public Button getCalculateButton() {
return mCalculateButton;
}
view raw 4.java hosted with ❤ by GitHub
Данные методы пригодятся нам в процессе написания теста.

Вернёмся обратно к классу MainActivityTest.java. Сперва получаем View объекты:
assertNotNull(mMainActivity);
final Button addButton = mMainActivity.getAddButton();
final EditText inputDataEditText = mMainActivity.getInputDataEditText();
view raw 5.java hosted with ❤ by GitHub
После чего используем метод runOnUiThread(Runnable action). Выполняем ряд действий. Стоит помнить, что тесты запускаются не в основном потоке, а все действия, связанные непосредственно с View, должны вызываться в основном потоке. Чтобы избежать ошибки, мы и используем данный метод.
В принципе, любые действия, связанные с интерфейсом, можно назвать моделью поведения пользователя
Виктор Мацкевич
Android-разработчик
mMainActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
inputDataEditText.setText(String.valueOf(2));
addButton.performClick();
inputDataEditText.setText(String.valueOf(2));
}
});
view raw 6.java hosted with ❤ by GitHub
Проверяем, правильно ли были представлены введённые данные, следующими операциями:
assertEquals(inputDataEditText.getText().toString(), "2");
assertEquals(mMainActivity.getTextResult(), "2.0 +");
assertTrue(mMainActivity.mFirstValue == 2);
assertTrue(mMainActivity.mSecondValue == 0);
view raw 7.java hosted with ❤ by GitHub
После чего получаем кнопку очистки поля и имитируем нажатие на кнопку методом performClick():
final Button cleanButton = mMainActivity.getCleanButton();
mMainActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
cleanButton.performClick();
}
});
view raw 8.java hosted with ❤ by GitHub
После нажатия должен сработать метод cleanData(). В данном методе происходит обнуление объектов, используемых как в интерфейсе, так и в ходе вычисления результата.
assertEquals(mMainActivity.getInputDataEditText().getText().toString(), "");
assertEquals(mMainActivity.getTextResult(), "");
assertTrue(mMainActivity.mFirstValue == 0);
assertTrue(mMainActivity.mSecondValue == 0);
view raw 9.java hosted with ❤ by GitHub
После выполнения всех операций мы должны получить следующий результат:
Данный лог информирует нас о том, что тест пройден успешно.

Также в коде теста вызывается функция getInstrumentation().waitForIdleSync(). Данная функция необходима для синхронизации потоков. Однако она ожидает завершения всех потоков, связанных с UI. Именно поэтому она не всегда будет срабатывать при выполнении тестов с асинхронными задачами. Этой теме будет посвящена отдельная статья.

Есть ещё немало особенностей в написании тестов, о них поговорим позже.
Спасибо за внимание!
BytePace © Все права защищены