Я не должен вам говорить, что тестирование является фундаментальной частью цикла разработки продукта, хотя я только что это сделал. Независимо от того, разрабатываетесь ли вы для настольного компьютера, Интернета или мобильного пространства, вы должны убедиться, что конечная система программного обеспечения работает в соответствии с требованиями. Этот блог будет проходить два варианта тестирования на платформе Android: единичное и функциональное.
Модульные тесты говорят разработчику, что код работает правильно, в то время как функциональные тесты говорят разработчику, что код делает то, что должен делать. Чтобы более четко понять разницу, я буду использовать аналогию, упомянутую в
http://www.ibm.com/developerworks/library/j-test.html . Если вы думаете о построении системы как о похожей на строительство дома, то подумайте о модульных тестах как о наличии инспектора по строительству на строительной площадке, сосредоточенного на том, чтобы убедиться, что внутренняя система дома (фундамент, водопровод и т. Д.) Работает правильно при функциональном тестировании, когда домовладелец посещает дом и интересуется тем, как выглядит дом, имеют ли комнаты нужный размер и т. д. Он предполагает, что внутренние функции дома работают должным образом.
В модульном тестировании думайте о модуле как о самой маленькой тестируемой части приложения, такой как функция / метод. Цель состоит в том, чтобы изолировать такие части и убедиться, что они работают правильно, имея возможность повторного тестирования. Модульные тесты написаны с точки зрения разработчика, и, как разработчик, мы должны убедиться, что понимаем спецификацию и требования нашей программной системы, прежде чем писать модульные тесты. Это может быть достигнуто с помощью вариантов использования.
Ход модульного тестирования показан ниже:
Android использует
JUnit и инфраструктуру с открытым исходным кодом для написания и запуска модульных тестов. Некоторые из функций фреймворка:
- Утверждения для тестирования ожидаемых результатов
- Тестовые приборы для обмена общими данными
- Тест бегунов для запуска тестов
Как работает JUnit? Вы используете операторы assert, чтобы утверждать, что что-то является истинным (assertTrue (ожидаемый, фактический), assertTrue (условие) и т. Д.), Что-то является ложным (assertFalse (условие) и т. Д.), Что-то равно (assertEqual (ожидаемый, фактический)) , и т.д). Когда утверждение не выполняется, тест не удался для этого конкретного случая, поэтому ваш код должен быть исправлен (при условии, что модульный тест был написан правильно).
Начиная с версии 4.x, JUnit использует преимущества аннотаций Java 5:
- @Тест
- Пометьте свои тестовые случаи аннотацией @Test
- @До и после
- Используется для методов «setup» и «tearDown»
- Они запускаются до и после каждого теста
- @BeforeClass и @AfterClass
- Используется для широких классов «setup» и «tearDown»
- Они запускаются один раз, до и после всех тестовых случаев
- Вы пишете код инициализации (т.е. открытое соединение с базой данных) и код очистки (то есть закрытое соединение с базой данных) здесь
- @Ignore
- Используется для тестовых случаев, которые вы хотите игнорировать (т. Е. Для методов, которые еще не полностью реализованы, следовательно, не готовы к запуску)
- Обработка исключений
- Используйте параметр «ожидается» с аннотацией @Test для тестовых случаев, которые ожидают исключений (т. Е. @Test (Ожидаемый = ArithmeticException.class) …)
Давайте посмотрим на пример Java, а именно приложение калькулятора. Класс, который включает в себя основные функции калькулятора, показан ниже:
package edu.fau.csi.junit;
/**
* Simple class that incorporates the basic functionality of a calculator, providing methods to
* add, subtract, multiply, and divide to double numbers.
*
* @author Mihai Fonoage
*
*/
public class Calculator {
/**
* Left operand of the operation to be performed.
*/
private double leftOperand;
/**
* Right operand of the operation to be performed.
*/
private double rightOperand;
/**
* Constructs a Calculator object by initializing the leftOperand and rightOperand
* with the given values.
*
* @param leftOperand Left operand.
* @param rightOperand Right operand.
*/
public Calculator(double leftOperand, double rightOperand) {
this.leftOperand = leftOperand;
this.rightOperand = rightOperand;
}
/**
* Adds the leftOperand to the rightOperand.
*
* @return The sum of the two operands.
*/
public double add() {
return leftOperand + rightOperand;
}
/**
* Subtracts the rightOperand from the leftOperand.
*
* @return The subtraction of the two operands.
*/
public double subtract() {
return leftOperand - rightOperand;
}
/**
* Multiply the leftOperand to the rightOperand.
*
* @return The multiplication of the two operands.
*/
public double multiply() {
return leftOperand * rightOperand;
}
/**
* Divides the leftOperand to the rightOperand.
*
* @return The division of the two operands.
*/
public double divide() {
if (rightOperand == 0) {
throw new ArithmeticException("right operand should not be zero!");
}
return leftOperand / rightOperand;
}
}
Я создал новую исходную папку с именем test и включил следующий класс теста (щелкнув правой кнопкой мыши на имени проекта -> New -> JUnit Test Case, выберите «Новый тест JUnit 4», и в качестве тестируемого класса найдите выше класса Калькулятор):
package edu.fau.csi.junit;
import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* JUnit Test case for the Calculator class.
*
* @author Mihai Fonoage
*
*/
public class CalculatorTest {
private Calculator calculator;
/**
* Sets up the test fixture.
* (Called before every test case method.)
*/
@Before
public void setUp() {
calculator = new Calculator(6, 4);
}
/**
* Tears down the test fixture.
* (Called after every test case method.)
*/
@After
public void tearDown() {
calculator = null;
}
/**
* Test method for {@link edu.fau.csi.junit.Calculator#add()}.
*/
@Test
public void testAdd() {
assertTrue(calculator.add() == 10);
}
/**
* Test method for {@link edu.fau.csi.junit.Calculator#subtract()}.
*/
@Test
public void testSubtract() {
assertTrue(calculator.subtract() == 2);
}
/**
* Test method for {@link edu.fau.csi.junit.Calculator#multiply()}.
*/
@Test
public void testMultiply() {
assertTrue(calculator.multiply() == 24);
}
/**
* Test method for {@link edu.fau.csi.junit.Calculator#divide()}.
*/
@Test
public void testDivide() {
assertTrue(calculator.divide() == 1.5);
}
/**
* Test method for {@link edu.fau.csi.junit.Calculator#divide()}.
*/
@Test(expected = ArithmeticException.class)
public void testDivideByZero() {
Calculator calculator = new Calculator(6, 0);
calculator.divide();
}
}
Чтобы запустить все, щелкните правой кнопкой мыши класс CalculatorTest -> Run As -> JUnit Test. Все пять тестов должны пройти.
Функциональные тесты написаны с точки зрения пользователя. Они подтверждают, что система делает то, что от нее ожидал пользователь, основываясь на функциональных требованиях системы, поэтому она фокусируется на поведении, которое интересует пользователей. Тестер взаимодействует с системой, чтобы определить, является ли поведение правильным.
В Android функциональное тестирование возможно с помощью пакета (ов) android.test. *. Мы собираемся использовать тот же пример калькулятора, адаптированный для Android. Когда вы создаете проект Android-калькулятор в Eclipse, выберите также создать для него тестовый проект. В итоге у вас будет два проекта: калькулятор и калькулятор. Макет проекта Калькулятор описан ниже:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:id="@+id/linear"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<TableLayout android:id="@+id/table"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:stretchColumns="1">
<TableRow>
<TextView android:id="@+id/leftOperand_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dip"
android:textStyle="bold"
android:text="Left Operand">
</TextView>
<EditText android:id="@+id/leftOperand"
android:padding="3dip"
android:numeric="decimal"
android:singleLine="true"
android:scrollHorizontally="true"
android:nextFocusDown="@+id/rightOperand">
</EditText>
</TableRow>
<TableRow>
<TextView android:id="@+id/rightOperand_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dip"
android:textStyle="bold"
android:text="Right Operand">
</TextView>
<EditText android:id="@+id/rightOperand"
android:padding="3dip"
android:singleLine="true"
android:scrollHorizontally="true"
android:nextFocusDown="@+id/plus">
</EditText>
</TableRow>
</TableLayout>
<LinearLayout android:id="@+id/buttons_linear"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button android:id="@+id/plus"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="6dip"
android:text="+"
android:layout_gravity="center_horizontal"
android:nextFocusRight="@+id/minus">
</Button>
<Button android:id="@+id/minus"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="6dip"
android:text="-"
android:layout_gravity="center_horizontal"
android:nextFocusRight="@+id/multiply">
</Button>
<Button android:id="@+id/multiply"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="6dip"
android:text="*"
android:layout_gravity="center_horizontal"
android:nextFocusRight="@+id/divide">
</Button>
<Button android:id="@+id/divide"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="6dip"
android:text="/"
android:layout_gravity="center_horizontal"
android:nextFocusRight="@+id/divide">
</Button>
<Button android:id="@+id/clear"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="6dip"
android:text="C"
android:layout_gravity="center_horizontal"
android:nextFocusRight="@+id/divide">
</Button>
</LinearLayout>
<TextView android:id="@+id/result"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="20dip"
android:textStyle="bold"
android:gravity="right"
android:text="Result">
</TextView>
</LinearLayout>
Это простой макет с двумя исходными текстовыми полями для левого и правого операнда, пятью кнопками, каждая для одной операции, плюс кнопка очистки и одно текстовое поле, в котором будет храниться результат вычисления. Как примечание, я поместил текст для каждого из этих элементов в XML-файл макета, чтобы сэкономить время; Вместо этого вы должны поместить их в файл strings.xml.
Класс CalculatorActivity описан ниже:
package edu.fau.csi.calculator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class CalculatorActivity extends Activity implements OnClickListener {
private EditText leftOperand;
private EditText rightOperand;
private TextView result;
private Calculator calculator;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.calculator);
leftOperand = (EditText)findViewById(R.id.leftOperand);
rightOperand = (EditText)findViewById(R.id.rightOperand);
result = (TextView)findViewById(R.id.result);
Button plus = (Button)findViewById(R.id.plus);
plus.setOnClickListener(this);
Button minus = (Button)findViewById(R.id.minus);
minus.setOnClickListener(this);
Button multiply = (Button)findViewById(R.id.multiply);
multiply.setOnClickListener(this);
Button divide = (Button)findViewById(R.id.divide);
divide.setOnClickListener(this);
Button clear = (Button)findViewById(R.id.clear);
clear.setOnClickListener(this);
}
@Override
public void onClick(View view) {
double leftOp = Double.parseDouble(leftOperand.getText().toString());
double rightOp = Double.parseDouble(rightOperand.getText().toString());
calculator = new Calculator(leftOp, rightOp);
if (view.getId() == R.id.plus) {
result.setText("" + calculator.add());
}
else if (view.getId() == R.id.minus) {
result.setText("" + calculator.add());
}
else if (view.getId() == R.id.multiply) {
result.setText("" + calculator.multiply());
}
else if (view.getId() == R.id.divide) {
result.setText("" + calculator.divide());
}
else if (view.getId() == R.id.clear) {
leftOperand.setText("");
rightOperand.setText("");
result.setText("");
leftOperand.requestFocus();
}
}
}
Класс Calculator, который фактически выполняет вычисления, точно такой же, как и в примере с Java.
Когда вы запустите этот пример, вы получите следующий экран:
Часть, посвященная модульному тестированию, аналогична той, которая упоминается в разделе блочного тестирования этого блога. Вы по-прежнему можете проверять свою бизнес-логику без необходимости взаимодействия с приложением через его пользовательский интерфейс. На самом деле, Calculator.java и CalculatorTest.java были просто скопированы из примера Java в Android без каких-либо изменений.
Функциональное тестирование для этого примера включает процесс ввода чисел в двух текстовых полях, нажатия одной из кнопок операций и проверки результата, чтобы убедиться, что он является ожидаемым. Вместо того, чтобы делать это разработчиком / пользователем / тестером вручную, мы можем автоматизировать задачу, отправляя ключевые события через инструментарий, все из класса CalculatorActivityTest из приложения CalculatorTest:
package edu.fau.csi.calculator.test;
import android.test.ActivityInstrumentationTestCase2;
import android.view.KeyEvent;
import android.widget.TextView;
import edu.fau.csi.calculator.CalculatorActivity;
public class CalculatorActivityTest extends ActivityInstrumentationTestCase2<CalculatorActivity>{
private TextView result;
private CalculatorActivity calculatorInstance;
public CalculatorActivityTest() {
super("edu.fau.csi.calculator", CalculatorActivity.class);
}
/* (non-Javadoc)
* @see android.test.ActivityInstrumentationTestCase2#setUp()
*/
@Override
protected void setUp() throws Exception {
// TODO Auto-generated method stub
super.setUp();
calculatorInstance = (CalculatorActivity) getActivity();
result = (TextView)calculatorInstance.findViewById(R.id.result);
}
/**
* Test the addition operation of the CalculatorActivity
*
* @throws Throwable
*/
public void testAdd() throws Throwable {
//First field value
sendKeys( KeyEvent.KEYCODE_3 );
sendKeys( KeyEvent.KEYCODE_PERIOD );
sendKeys( KeyEvent.KEYCODE_5 );
//Move to the second field
sendKeys( KeyEvent.KEYCODE_DPAD_DOWN );
sendKeys( KeyEvent.KEYCODE_2 );
sendKeys( KeyEvent.KEYCODE_PERIOD );
sendKeys( KeyEvent.KEYCODE_1 );
//Move to the '+' button
sendKeys( KeyEvent.KEYCODE_DPAD_DOWN );
sendKeys( KeyEvent.KEYCODE_DPAD_CENTER );
//Wait for the activity to finish all of its processing.
getInstrumentation().waitForIdleSync();
//Use assertion to make sure the value is correct
assertTrue(result.getText().toString().equals("5.6"));
}
}
Приведенный выше класс тестирования активности наследуется от ActivityInstrumentationTestCase2, который обеспечивает функциональное тестирование одной операции, а именно нашей CalculatorActivity. Приведенный выше код только проверяет метод add, но аналогичные реализации могут быть выполнены для всех остальных трех вычислений.
Файл манифеста Android для проекта CalculatorTest описан ниже:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="edu.fau.csi.calculator.test"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<uses-library android:name="android.test.runner" />
</application>
<uses-sdk android:minSdkVersion="4" />
<instrumentation android:targetPackage="edu.fau.csi.calculator"
android:name="android.test.InstrumentationTestRunner" />
</manifest>
Когда действие запускается в первый раз, вам необходимо создать новую конфигурацию Android JUnit Test. Есть две возможности, обе показаны ниже:
или
Когда вы запустите это, приложение «Калькулятор» запустится в эмуляторе, вы увидите два текстовых поля, заполненные значениями 3.5 и 2.1, нажатие кнопки «+» и поле результата, заполненное результатом операции, а именно 5.6.
Я надеюсь, что вы найдете это полезным!