2. JUnit (Модульное тестирование)
.1 Описание
Оболочки модульного тестирования - это программные средства для разработки тестов, включающие: построение, выполнение тестов и создание отчетов.
Первая оболочка модульного тестирования SUnit была создана Кентом Беком в 1999 году для языка Smalltalk. Позднее Эрик Гамма создал JUnit. Сейчас существует множество оболочек для модульного тестирования, которые известны как программные средства семейства XUnit. JUnit - реализация XUnit, наиболее широко используемая и расширенная версия оболочек модульного тестирования. JUnit создан на языке Java и используется для тестирования Java кода.
Модульное тестирование или unit testing - процесс проверки на корректность функционирования отдельных частей исходного кода программы путем запуска тестов в искусственной среде. Под частью кода в Java следует понимать исполняемый компонент. С помощью модульного тестирования обычно тестируют низкоуровневые элементы кода - такие, как методы. JUnit позволяет вне исследуемого класса создавать тесты, при выполнении которых произойдет корректное завершение программы. Кроме основного положительного сценария может выполняться проверка работоспособности системы в альтернативных сценариях, например, при генерации методом исключения как реакция на ошибочные исходные данные.
Оценивая каждую часть кода изолированно и подтверждая корректность ее работы, точно установить проблему значительно проще, чем если бы элемент был частью системы.
Технология позволяет и предлагает сделать более тесной связь между разработкой кода и его тестированием, а также предоставляет возможность проверить корректность работы класса, не прибегая к пробному выводу при отладке кода.4 в отличие от JUnit 3 полностью построен на аннотациях.
Для использования технологии необходимо загрузить библиотеку JUnit с сервера junit.org и включить архив junit.jar в список библиотек приложения.
При включении модульного тестирования в проект:
тесты разрабатываются для нетривиальных методов системы;
ошибки выявляются в процессе проектирования метода или класса;
в первую очередь разрабатываются тесты на основной положительный сценарий;
разработчику приходится больше уделять внимания альтернативным сценариям поведения, так как они являются источником ошибок, выявляемых на поздних стадиях разработки;
разработчику приходится создавать более сфокусированные на своих обязанностях методы и классы, так как сложный код тестировать значительно труднее;
снижается число новых ошибок при добавлении новой функциональности;
устаревшие тесты можно игнорировать;
тест отражает элементы технического задания, то есть некорректное завершение теста сообщает о нарушении технических требований заказчика;
каждому техническому требованию соответствует тест;
получение работоспособного кода с наименьшими затратами.
.2 Аннотация @Test
Аннотация помечает метод как тестовый, что позволяет использовать возможности класса org.junit.Assert и запускать его в режиме тестирования. Метод, предназначенный для функционирования в качестве теста, достаточно промаркировать аннотацией @Test.
Тестовый метод должен всегда объявляться как public void. Аннотация может использовать параметры:- определяет ожидаемый класс исключения;- определяет время, превышение которого делает тест ошибочным.
.3 Наиболее часто
используемые методы
Метод assertEquals() проверяет на равенство значений expected и actual с возможной погрешностью delta. При выполнении заданных условий сообщает об успешном завершении, в противном случае - об аварийном завершении теста. При аварийном завершении генерируется ошибка java.lang.AssertionError.
Все методы класса Assert в качестве возвращаемого значения имеют тип void.
Среди них можно выделить:
assertTrue(boolean condition)/assertFalse(boolean condition) - проверяет на истину/ложь значение condition;
assertSame(Object expected, Object actual) - проверяет, ссылаются ли ссылки на один и тот же объект;
assertNotSame(Object unexpected, Object actual) - проверяет, ссылаются ли ссылки на различные объекты;
assertNull(Object object)/assertNotNull(Object object) - проверяет, имеет или не имеет ссылка значение null;
assertThat(T actual, Matcher<T> matcher) - проверяет выполнение условия;() - вызывает ошибку, используется для проверки, достигнута ли определенная часть кода или для заглушки, сообщающей, что тестовый метод пока не реализован.
Все перечисленные методы имеют перегруженную версию с параметром типа String в первой позиции, в который можно передавать сообщение, выводимое при аварийном завершении теста.
В дополнение к классу Assert с его методами жесткой проверки прохождения теста разработан класс org.junit.Assume. Методы этого класса в случае невыполнения предполагаемого условия при работе теста сообщают только о том, что предположение не исполнилось, не генерируя при этом никаких ошибок. Методы класса предполагают, что:(Throwable t) - тестируемый метод завершится, не вызвав исключения;(Object…objects) - передаваемый аргумент(ы) не является ссылкой на null;
assumeThat(T actual, Matcher<T> matcher) - условие выполнится;
assumeTrue(boolean b) - значение передаваемого аргумента истинно.
.4 Фикстуры
Фикстура (Fixture) - состояние среды тестирования, которое требуется для успешного выполнения тестового метода. Может быть представлено набором каких-либо объектов, состоянием базы данных, наличием определенных файлов, соединений и проч.
В версии JUnit 4 аннотации позволяют исполнять одну и ту же фикстуру для каждого теста или всего один раз для всего класса, или не исполнять ее совсем.
Предусмотрено четыре аннотации фикстур - две для фикстур уровня класса и две для фикстур уровня метода.
@BeforeClass - запускается только один раз при запуске теста.
@Before - запускается перед каждым тестовым методом (используется, если необходимо выполнить какое-либо повторяющееся действие).
@After - запускается после каждого метода.
@AfterClass - запускается после того, как отработали все тестовые методы.
Использование фикстур позволяет выделить этапы и точно определять моменты, например, создания/удаления объекта, инициализации необходимых ресурсов, очистки памяти и прочего.
.5 Тестирование
исключительных ситуаций
При тестировании альтернативных сценариев работы метода часто требуется точно определить тип генерируемого методом исключения на основе переданных некорректных параметров. Если тест выдает исключение, то инфраструктура тестирования сообщает о корректном результате его исполнения.
Аннотацию @Test при необходимости тестирования генерации конкретного исключения (например, деление на 0) следует использовать с параметром expected. Параметр предназначен для задания типа исключения, которое данный тест должен генерировать в процессе своего выполнения.
.6 Ограничение по времени
2.7 Игнорирование
тестов
При контроле корректности функционирования бизнес-логики приложений до появления JUnit 4 игнорирование неудачных, незавершенных или устаревших тестов представляло определенную проблему. Аннотация @Ignore заставляет инфраструктуру тестирования проигнорировать данный тестовый метод.
Аннотация предусматривает наличие комментария о причине игнорирования теста, полезного при следующем к нему обращении.
3. Примеры
Рассмотрим первый пример: в программе имеется 2 метода, которые получают на вход целые числа. В первом случае результатом является умножение чисел, во втором - деление.
Код программы:
package Clc;
public class Main {
public void main(String[] args){
this.mult(2, 5);
this.del(6, 3);
}
public int mult(int a, int b){
return a*b;
}
public double del(int a, int b){
return a/b;
}
}
Теперь рассмотрим код тестового класса:
package Clc;
import static org.junit.Assert.*;
import org.junit.Test;
public class MainTest {
@Test
public void testMult() {c = new Main();
int prod = c.mult(3, 5);("Умножение выполнено неверно", prod, 16);
}
@Test
public void testDel() {c = new Main();("Деление неверно",c.del(20, 2),11);
}
@Test
public void testDelEx() {c = new Main();
try{.del(2, 0);("Деление на 0");
}
catch(Exception e){.out.println("Попытка деления на 0");
}
}
@Test (expected = Exception.class)
public void testDelNol(){
new Main().del(3, 0);
}
}
Во-первых мы импортируем две библиотеки, которые нам понадобятся для выполнения тестов: org.junit.Assert.* и org.junit.Test. Это основные библиотеки для создания тестов в JUnit. В нашем случае работа ведётся на JUnit 4.11.
Методом assertEquals() тестового метода testMult() мы сравниваем действительное значение (то, которое получили в ходе выполнения метода mult() класса Main.java) с тем значением, которое нужно получить на выходе (в нашем случае значение заведомо ложное).
Методом testDel() делаем то же самое только для деления (тестируем метод del() класса Main.java).
Третий метод - testDelNol() как раз показывает тестирование исключений, когда программа каким-то образом может пропустить деление на ноль. После аннотации теста можно увидеть дополнительный параметр (expected = Exception.class. Он говорит о том, что мы заведомо ожидаем ошибку выполнения теста.
Тестировать исключения можно и другим способом, как показано в методе testDelEx(). Здесь уже кому как удобно.
Как можно заметить из кода тестового класса, каждая отдельная операция тестируется в отдельном методе. Это делается потому, что если у Вас в одном методе будет тестироваться несколько операций, то при первом невыполнении какого-либо условия дальнейшие условия тестового метода проверяться не будут. Это не страшно, программа небольшая программа. Но подумайте, что будет, если у Вас 100 или более тестов. Трудозатраты на выявление ошибки будут очень велики.
Посмотрим на выполнение этого
тестового класса:
Последние 2 теста выполнены успешно, так как на выходе мы получили ожидаемую ошибку.
Рассмотри второй пример. Здесь будет простое матричное умножение.
Вспомогательный класс заполнения матриц:
package matrix_m;
public class MMult {
int m,n;
public MMult(int columns, int rows){= columns;= rows;= new double [m][n];
}
double [][] matrix;
int rows(){
return n;
}
int column(){
return m;
}
double element (int column, int row){
return matrix[column][row];
}
}
double[] row(int index){
return matrix[index];
}
double[] column(int index){
double[] tmp = new double[this.n];
for (int i = 0; i < index; i++)[i][index] = tmp[i];
return tmp;
}
}
Основной класс:
package matrix_m;
import java.util.Scanner;
public class Main {
public Scanner in = new Scanner(System.in);
public int row1, col2, col, row;
public static void main(String[] args){mmultiply = new Main();first = mmultiply.read();second = mmultiply.read();prod = mmultiply.prod(first, second);
if (prod == null).out.println("Эти матрицы не могут быть перемножены");
else.print(prod);
}read(){
System.out.println("Введите размерность матрицы");
col = in.nextInt();= in.nextInt();mx = new MMult(col,row);
if(mx.m != 0 || mx.n != 0){.out.println("Введите элементы этого массива");
for (int i = 0; i < mx.column(); i++){
for (int j = 0; j < mx.rows(); j++){
double tmp = in.nextDouble();.update(i, j, tmp);
}
}
}
else{.out.println("Введена нулевая размерность");
}
return mx;
}
public void print(MMult matrix){
for (int i = 0; i < matrix.column(); ++i, System.out.println())
for (int j = 0; j < matrix.rows(); ++j).out.printf(" %10.4f", matrix.element(i, j));
}prod(MMult first, MMult second){
int col1 = first.column(), row2 = second.rows();= first.rows();= second.column();res = new MMult(first.column(),second.rows());
if(row1 == col2){
for (int i = 0; i < col1; i++)
for(int k = 0; k < row2; k++)
for (int j = 0; j < col2; j++).update(i, k, res.element(i, k)+ first.element(i, j) * second.element(i, k));
return res;
}
else
return null;
}
}
Тестовый класс:
import static org.junit.Assert.*;
import org.junit.Test;
public class MainTest {pr = new Main();first = pr.read();second = pr.read();
@Test
public void testMain() {("Матрицы не перемножены",pr.prod(first, second)== null);
}
@Test
public void testRead() {("Введено не числовое значение", pr.in.hasNextInt() == false);
if(pr.col == 0 || pr.row == 0){("Введена нулевая размерность");
}
}
@Test
public void testProd() {
assertTrue("Колличество строк первой и столбцов второй матриц не совпадают", pr.row1 != pr.col2);
}
}
Здесь при тестировании мы проверяем были ли умножены матрицы, (testMain()), на введении нулевой размерности матрицы или не числового значения(testRead()), и на то, могут ли быть матрицы перемножены(testProd()).
Выполнение теста(для просмотра
результата все значения были введены ложные):

В результате проделанной работы
были изучены основы тестирования программного обеспечения. Используя полученные
знания, был написан программный комплекс. Особое внимание было уделено оболочке
модульного тестирования JUnit для тестирования программного обеспечения при
разработке. В дальнейшем планируется изучить тестирование Web-приложений.
Список использованных источников
1. Степанченко И.В. Методы тестирования программного обеспечения: Учеб. пособие / Степанченко И.В. - ВолгГТУ, Волгоград,2006. - 76 с.
. Синицын, С. В. Верификация программного обеспечения. / С.В. Синицин, Н.Ю. Налютин. - М.:2006. - 158 с.
. Канер, С. Тестирование программного обеспечения. Фундаментальные концепции менеджмента бизнес-приложений / С. Канер, Дж. Фолк, Е. Кек
. Тамре, Л. Введение в тестирование программного обеспечения / Л. Тамре; Пер. с англ. М.: Издательский дом "Вильямс", 2003. 368 с.
. Петренко А., Бритвина Е., Грошев С., Монахов А. Тестирование на основе моделей // «Открытые системы», 2003.
. Дастин Э., Рэшка Д., Пол Д. Автоматизированное тестирование программного обеспечения. Внедрение, управление и эксплуатация. -М.: Изд-во Лори, 2003. 567 с.
. Макгрегор Д., Сайке Д. Тестирование объектно-ориентированного программного обеспечения. Практическое пособие. К.: ООО «ТИД «ДС», 2002. - 432с.