Автоматизированные модульные тесты для приложения Android необходимы для его долгосрочного качества. Модульные тесты помогают протестировать одну единицу вашего кода (например, класс). Это помогает в обнаружении и выявлении ошибок или регрессий на самых ранних этапах цикла разработки. В этой статье мы увидим, как мы можем написать модульные тесты для нашего приложения для Android. В Android модульные тесты могут быть двух типов:
- Локальные модульные тесты — которые выполняются на самой машине разработки, а не на реальной машине
- Инструментированный юнит-тест — который работает на реальном устройстве Android.
Создание юнит-тестов с объектами JUnit и Mocking с помощью Mockito
Мы начнем с создания локальных модульных тестов для приложения Android, которое добавляет два числа.
Приложение содержит следующий вспомогательный класс
package com.testsinandroid;
import android.support.annotation.VisibleForTesting;
public class NumberAdder {
private final MainActivity mMainActivity;
public NumberAdder(MainActivity activity) {
mMainActivity = activity;
}
public void performAddition() {
double number1 = mMainActivity.getFirstNumber();
double number2 = mMainActivity.getSecondNumber();
if(!isNumberValid(number1) || !isNumberValid(number2)) {
throw new RuntimeException("invalid numbers");
}
double result = number1 + number2;
mMainActivity.setAdditionResult(result);
}
@VisibleForTesting
boolean isNumberValid(double number) {
if(number > 0) {
return true;
} else {
return false;
}
}
}
А также содержит активность, которая использует этот класс
package com.testsinandroid;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends Activity {
EditText firstNumber;
EditText secondNumber;
TextView addResult;
Button btnAdd;
NumberAdder numberAdder = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
firstNumber = (EditText)findViewById(R.id.txtNumber1);
secondNumber = (EditText)findViewById(R.id.txtNumber2);
addResult = (TextView)findViewById(R.id.txtResult);
btnAdd = (Button)findViewById(R.id.btnAdd);
if(numberAdder == null) {
numberAdder = new NumberAdder(this);
}
btnAdd.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
numberAdder.performAddition();
}
});
}
public double getFirstNumber() {
return Double.parseDouble(firstNumber.getText().toString());
}
public double getSecondNumber() {
return Double.parseDouble(secondNumber.getText().toString());
}
public void setAdditionResult(double result) {
addResult.setText(Double.toString(result));
}
}
План действий выглядит следующим образом
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<EditText
android:id="@+id/txtNumber1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="2"
android:inputType="number" >
<requestFocus />
</EditText>
<EditText
android:id="@+id/txtNumber2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/txtNumber1"
android:ems="2"
android:inputType="number" >
</EditText>
<Button
android:id="@+id/btnAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="46dp"
android:layout_below="@+id/txtNumber2"
android:text="Add" />
<TextView
android:id="@+id/txtResult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btnAdd"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
Когда наше приложение будет готово, у нас есть два основных модуля для тестирования в нашем коде.
- NumberAdder — это простой Java-класс, который использует MainActivity в качестве зависимости.
- MainActivity — действие, отображающее пользовательский интерфейс.
Так как NumberAdder
Junit для тестирования таких классов.
JUnit — это простая структура для написания повторяющихся модульных тестов. Когда мы тестируем один модуль (в данном случае NumberAdder), все другие классы, от которых зависит NumberAdder, могут быть отключены. Хорошей платформой для макетирования зависимостей в Java является Mockito . Чтобы добавить JUnit и Mockito в качестве тестовых зависимостей в наш проект, добавьте в build.gradle следующее
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.0.1'
// Required -- JUnit 4 framework
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
}
Чтобы написать тест Junit + Mockito для NumberAdder, создайте файл NumberAdderTest.java в папке
src / test / java / com / testsinandroid со следующим содержимым
package com.testsinandroid;
import com.testsinandroid.MainActivity;
import com.testsinandroid.NumberAdder;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class NumberAdderTest {
@Mock
MainActivity mMockMainActivity;
@Test
public void testIsNumberValid() {
//setup
//test
NumberAdder numberAdder = new NumberAdder(mMockMainActivity);
assert(numberAdder.isNumberValid(55.0));
}
@Test
public void testIsNumberNotValid() {
//setup
//test
NumberAdder numberAdder = new NumberAdder(mMockMainActivity);
assertFalse(numberAdder.isNumberValid(-55.0));
}
@Test
public void testPerformAddition() {
//setup
when(mMockMainActivity.getFirstNumber())
.thenReturn(10.0);
when(mMockMainActivity.getSecondNumber())
.thenReturn(11.0);
//test
NumberAdder numberAdder = new NumberAdder(mMockMainActivity);
numberAdder.performAddition();
//verify
verify(mMockMainActivity).setAdditionResult(21.0);
}
}
Приведенный выше код создает три теста, которые аннотируются аннотацией @Test. Тест запускается с использованием MockitoJUnitRunner. Этот бегун вводит фиктивный объект для каждого поля, помеченного @Mock. MainActivity подвергается насмешкам в приведенном выше тесте. В первых двух тестах мы делаем утверждение на основе значения, возвращаемого функцией. В третьем тесте мы устанавливаем значения, которые должны возвращаться при вызове getFirstNumber и getSecondNumber. Затем мы проверяем, имеет ли метод setAdditionResult правильное значение для mMockMainActivity.
После того, как мы написали эти локальные тесты JUnit, мы можем запустить их с помощью команды gradle. Сборка будет успешной, если все тесты пройдут или не пройдут, если они не пройдут.
./gradlew test
Написать тесты с использованием Roboelectric
Robolectric — это инфраструктура модульного тестирования для Android. С Roboelectric вы можете запустить тестовый модуль Android на JVM на вашей рабочей станции. Это очень удобно для модульного тестирования кода Android на рабочей станции. Сборка может быть неудачной, если тесты не пройдены.
Чтобы добавить Roboelectric к своим тестовым зависимостям, обновите свои зависимости следующим образом
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.0.1'
// Add Junit and mockito as testing dependencies
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
//For Roboelectric
testCompile "org.robolectric:robolectric:3.0"
}
Как только мы добавили зависимость, мы можем добавить следующий тест для MainActivity.
package com.testsinandroid;
import org.junit.Test;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import static org.junit.Assert.*;
import static org.junit.Assert.assertTrue;
import org.robolectric.RobolectricGradleTestRunner;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
MainActivity activity;
EditText firstNumber;
EditText secondNumber;
TextView addResult;
Button btnAdd;
@Before
public void setUp() {
activity = Robolectric.setupActivity(MainActivity.class);
firstNumber = (EditText)activity.findViewById(R.id.txtNumber1);
secondNumber = (EditText)activity.findViewById(R.id.txtNumber2);
addResult = (TextView)activity.findViewById(R.id.txtResult);
btnAdd = (Button) activity.findViewById(R.id.btnAdd);
}
@Test
public void testMainActivityAddition() {
//setup
firstNumber.setText("12.2");
secondNumber.setText("13.3");
//test
btnAdd.performClick();
//verify
assertEquals(25.5, Double.parseDouble(addResult.getText().toString()), 0.0);
}
}
В приведенном выше тесте мы создаем MainActivityTest. Он запускается с помощью RobolectricGradleTestRunner.
Затем мы используем Roboelectric API ‘Robolectric.setupActivity’, который создает действие. Он также вызывает свои методы жизненного цикла, такие как «OnCreate», «onStart» и т. Д. Затем мы получаем различные элементы представления в действии и устанавливаем значения. Как только значения установлены, мы нажимаем на кнопку. Затем мы наконец проверяем, что у результата TextView есть соответствующее значение, используя ‘assertEquals’.
Как показано в приведенном выше примере, Roboelectric позволяет легко написать модульный тест для вашего кода Android.
Написание тестов Android Instrumentation
Android позволяет писать тестовые модули инструментов. Это тесты, которые работают на реальном устройстве Android. Поскольку вы тестируете на реальном устройстве, вам не нужно издеваться над классами Android. Тестирование компонентов Android, таких как активность и т. Д., Становится проще с помощью теста на интрументацию. Но проблема в том, что вам нужно реальное устройство для запуска теста. Они не могут запустить его на вашей рабочей станции.
Чтобы добавить инструментарий, обновите зависимости Gradle следующим образом.
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.0.1'
// Add Junit and mockito as testing dependencies
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
//For Roboelectric
testCompile "org.robolectric:robolectric:3.0"
//For Instrumentation tests
androidTestCompile 'com.android.support:support-annotations:25.0.1'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
}
Чтобы написать тест на проверку подлинности для MainActivity, создайте файл MainActivityIntrumentationTest.java. Файл должен быть в папке
src / androidTest / java / com / testsinandroid со следующим содержимым
package com.testsinandroid;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.test.TouchUtils;
import android.test.ViewAsserts;
import android.view.View;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import static org.junit.Assert.*;
import android.test.UiThreadTest;
public class MainActivityIntrumentationTest extends ActivityInstrumentationTestCase2<MainActivity> {
MainActivity activity;
EditText firstNumber;
EditText secondNumber;
TextView addResult;
Button btnAdd;
public MainActivityIntrumentationTest() {
super(MainActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(true);
activity = getActivity();
firstNumber = (EditText)activity.findViewById(R.id.txtNumber1);
secondNumber = (EditText)activity.findViewById(R.id.txtNumber2);
addResult = (TextView)activity.findViewById(R.id.txtResult);
btnAdd = (Button) activity.findViewById(R.id.btnAdd);
}
@UiThreadTest
public void testMainActivityAddition() {
//setup
firstNumber.setText("12.2");
secondNumber.setText("13.3");
//test
btnAdd.performClick();
//verify
assertEquals(25.5, Double.parseDouble(addResult.getText().toString()), 0.0);
}
}
В приведенном выше коде наш тестовый класс наследуется от ActivityInstrumentationTestCase2. ActivityInstrumentationTestCase2 позволяет нам написать тест для Activity. В конструкторе этого класса мы должны пройти тестируемое действие («MainActivity» в нашем случае). Затем мы переопределяем метод ‘setUp’, в котором мы получаем объект действия и другие элементы пользовательского интерфейса действия. Затем мы пишем тест, который похож на тот, который мы написали в предыдущем разделе. Тест имеет аннотацию «@UiThreadTest». Это сделает этот тест выполненным в потоке пользовательского интерфейса, так как в этом тесте есть несколько операций пользовательского интерфейса.
Мы можем запустить тестирование после подключения устройства с помощью команды
./gradlew connectedCheck
Вывод
В этой статье мы увидели много способов написания модульных тестов для вашего кода Android. В зависимости от ваших потребностей в проекте, вы можете иметь один или несколько типов тестов в вашем проекте. Автоматизированные модульные тесты имеют много долгосрочных преимуществ. Их следует рассматривать и оценивать как часть ваших усилий по разработке. Описанные выше фреймворки помогают писать модульные тесты для вашего приложения для Android. Они делают большую часть тяжелой работы под капотом для вас, чтобы вы могли сосредоточиться на тесте. Так что получайте удовольствие от написания юнит-тестов в следующем Android-приложении.