Статьи

Модульное и функциональное тестирование в Android


Я не должен вам говорить, что тестирование является фундаментальной частью цикла разработки продукта, хотя я только что это сделал.
Независимо от того, разрабатываетесь ли вы для настольного компьютера, Интернета или мобильного пространства, вы должны убедиться, что конечная система программного обеспечения работает в соответствии с требованиями. Этот блог будет проходить два варианта тестирования на платформе 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.

Я надеюсь, что вы найдете это полезным!