Unit test java mock

JUnit и фреймворк Mockito

Разработчикам программного обеспечения на разных этапах своей деятельности приходится сталкиваться с тремя стратегиями тестирования : функциональным, интеграционным и модульным тестированием. Все они используются для тестирования приложений разными способами, и каждая из стратегий имеет определенную цель. К сожалению, ни одна из стратегий не даёт стопроцентной гарантии обнаружения всех имеющихся ошибок. И даже комбинация всех трёх стратегий не может дать такой гарантии. Но их сочетание позволяет существенно снизить количество ошибок и убедить разработчика, что приложение функционирует согласно предъявленным требованиям.

Функциональное тестирование

Проведение функционального тестирования, как правило, связано с созданием специальной группы специалистов, занимающихся тестированием. На этом этапе приложение развертывается в действующем окружении и проверяется его соответствие ТЗ (Техническому Заданию) и предъявленным функциональным требованиям. Команда тестеровщиков использует комплекс автоматизированных и ручных тестов.

Автоматизировать процесс функционального тестирования можно, если приложение включает API (Application Programming Interface) — интерфейс прикладного программирования, на котором оно построено. Однако наличие интерфейса в приложении (desktop, web) существенно снижает возможности полной автоматизации данного процесса.

Интеграционное тестирование

Стратегия интеграционного тестирования основывается на проверке прикладного кода в окружении, близком к фактическому окружению, но не являющимся им. Главная цель данной стратегии – убедиться в правильности взаимодействия кода с внешними ресурсами и взаимодействия различных технологий в приложении между собой.

Читайте также:  Зажать кнопку мыши python

В интеграционном тестировании не требуется использовать фиктивные данные, как при модульном тестировании. Вместо этого в интеграционных тестах часто используются базы данных, находящиеся в памяти, которые легко можно создавать и уничтожать во время выполнения тестов. База данных в памяти – это самая настоящая база данных, что дает возможность проверить правильность работы сущностей JPA. Но все же эта база данных не совсем настоящая – она лишь имитирует настоящую базу данных для целей интеграционного тестирования.

Модульное тестирование

Целью модульного тестирования является проверка работы прикладной логики всего приложения или отдельных его частей при разных исходных данных, и анализ правильности получаемых результатов. Несмотря на то, что цель модульного тестирования выглядит простой и понятной, реализация этого типа тестирования может оказаться очень сложным и запутанным делом, особенно при наличии «старого» кода. Основные приемы проведения модульного тестирования опираются на следующие базовые принципы :

  • внешние ресурсы не используются, т.е. недопустимо подключение к базам данных, веб-службам и т.п.;
  • каждый класс имеет свой тест;
  • тестируются только общедоступные методы или интерфейсы, а внутренний код тестируется за счет изменения входных данных;
  • для получения данных, требуемых тестируемой логике, должны создаваться фиктивные зависимости.

При проведении модульного тестирования для создания фиктивных классов-зависимостей можно использовать простой, но мощный фреймворк Mockito совместно с JUnit.

Фреймворк Mockito

Фреймворк Mockito предоставляет ряд возможностей для создания заглушек вместо реальных классов или интерфейсов при написании JUnit тестов. Mockito можно скачать с сайта https://code.google.com/p/mockito, либо определить в зависимостях (dependencies) в maven проекте :

 org.mockito mockito-core 1.9.5 test  

Наибольшее распространение получили следующие возможности Mockito :

  • создание заглушек для классов и интерфейсов;
  • проверка вызыва метода и значений передаваемых методу параметров;
  • использование концепции «частичной заглушки», при которой заглушка создается на класс с определением поведения, требуемое для некоторых методов класса;
  • подключение к реальному классу «шпиона» spy для контроля вызова методов.

Синтаксис создания заглушки Mockito

Чтобы создать Mockito объект можно использовать либо аннотацию @Mock, либо метод mock. В следующем примере в двух разных классах (Test_Mockito1, Test_Mockito2) разными способами создаются объекты mcalc для имитации интерфейса калькулятора ICalculator.

import org.mockito.Mock; import org.mockito.Mockito; import com.example ICalculator; public class Test_Mockito1 < @Mock ICalculator mcalc; >———————————————————— public class Test_Mockito2

Если использовать статический импорт Mockito, то синтаксис будет иметь следующий вид :

import static org.mockito.Mockito.*; import com.example ICalculator; public class Test_Mockito

ПРИМЕЧАНИЕ : помните, что методы mock объекта возвращают значения по умолчанию : false для boolean, 0 для int, пустые коллекции, null для остальных объектов.

Методы Mockito в примерах

Для рассмотрения методов фреймворка Mockito будем использовать в качестве тестового класса калькулятор, реализующий интерфейс ICalculator. В следующем коде представлены листинги интерфейса ICalculator и класса Calculator.

Листинги тестового класса и интерфейса

public interface ICalculator < public double add (double d1, double d2); public double subtract (double d1, double d2); public double multiply (double d1, double d2); public double divide (double d1, double d2); >//--------------------------------------------------- public class Calculator < ICalculator icalc; public Calculator(ICalculator icalc) < this.icalc = icalc; >public double add(double d1, double d2) < return icalc.add(d1, d2); >public double subtract(double d1, double d2) < return icalc.subtract(d1, d2); >public double multiply(double d1, double d2) < return icalc.multiply(d1, d2); >public double divide(double d1, double d2) < return icalc.divide(d1, d2); >public double double15() < return 15.0; >>

Как видно из листингов все методы калькулятора (add, subtract, multiply, divide), за исключением одного, возвращают не вычисленные значения, а результаты выполнения данных методов в объекте, реализующим интерфейс ICalculator, который в наших тестах будет представлять заглушка mcalc. Последний метод double15() должен вернуть реальное значение.

1. Определение поведения — when(mock).thenReturn(value)

Этот метод позволяет определить возвращаемое значение при вызове метода mock с заданными параметрами. Если будет указано более одного возвращаемого значения, то они будут возвращены методом последовательно, пока не вернётся последнее; после этого при последующих вызовах будет возвращаться только последнее значение. Таким образом, чтобы метод всегда возвращал одно и то же значение, седует просто определить одно условие.

@RunWith(MockitoJUnitRunner.class) public class Test_Mockito < @Mock ICalculator mcalc; // используем аанотацию @InjectMocks для создания mock объекта @InjectMocks Calculator calc = new Calculator(mcalc); @Test public void testCalcAdd() < // определяем поведение калькулятора для операции сложения when(calc.add(10.0, 20.0)).thenReturn(30.0); // проверяем действие сложения assertEquals(calc.add(10, 20), 30.0, 0); // проверяем выполнение действия verify(mcalc).add(10.0, 20.0); // определение поведения с использованием doReturn doReturn(15.0).when(mcalc).add(10.0, 5.0); // проверяем действие сложения assertEquals(calc.add(10.0, 5.0), 15.0, 0); verify(mcalc).add(10.0, 5.0); >>

Метод verify позволяет проверить, была ли выполнена проверка с определенными параметрами. Если проверка не выполнялась или выполнялась с другими параметрами, то verify вызовет исключение.

Для определения поведения mock в тесте была использована также следующая конструкция :

doReturn(value).when(mock).method(params)

2. Подсчет количества вызовов — atLeast, atLeastOnce, atMost, times, never

Для проверки количества вызовов определенных методов Mockito предоставляет следующие методы :

  • atLeast (int min) — не меньше min вызовов;
  • atLeastOnce () — хотя бы один вызов;
  • atMost (int max) — не более max вызовов;
  • times (int cnt) — cnt вызовов;
  • never () — вызовов не было;

Следующий тест демонстрирует контроль количества вызовов метода subtract с разными параметрами. Для этого сначала определяется поведение mock (при определенных параметрах выдавать соответствующие результаты), и выполняются проверки с использованием assertEquals. После этого выполняется проверка количества вызовов mock с определенными параметрами. Две последние проверки не выполняются — «закомментированы». Если снять комментарий хотя бы с одной из них, то метод verify, вызовет исключение. Комментарий к этим проверкам описывает причину вызова методом исключения.

@Test public void testCallMethod() < // определяем поведение (результаты) when(mcalc.subtract(15.0, 25.0)).thenReturn(10.0); when(mcalc.subtract(35.0, 25.0)).thenReturn(-10.0); // вызов метода subtract и проверка результата assertEquals (calc.subtract(15.0, 25.0), 10, 0); assertEquals (calc.subtract(15.0, 25.0), 10, 0); assertEquals (calc.subtract(35.0, 25.0),-10, 0); // проверка вызова методов verify(mcalc, atLeastOnce()).subtract(35.0, 25.0); verify(mcalc, atLeast (2)).subtract(15.0, 25.0); // проверка - был ли метод вызван 2 раза? verify(mcalc, times(2)).subtract(15.0, 25.0); // проверка - метод не был вызван ни разу verify(mcalc, never()).divide(10.0,20.0); /* Если снять комментарий со следующей проверки, то * ожидается exception, поскольку метод "subtract" * с параметрами (35.0, 25.0) был вызван 1 раз */ // verify(mcalc, atLeast (2)).subtract(35.0, 25.0); /* Если снять комментарий со следующей проверки, то * ожидается exception, поскольку метод "subtract" * с параметрами (15.0, 25.0) был вызван 2 раза, а * ожидался всего один вызов */ // verify(mcalc, atMost (1)).subtract(15.0, 25.0); >

3. Обработка исключений — when(mock).thenThrow()

Mockito позволяет вызвать исключение при определенных условиях. Для этого необходимо использовать следующий синтаксис кода :

// создаем исключение RuntimeException exception = new RuntimeException ("Division by zero"); // определение поведения для вызова исключения doThrow(exception).when(mock).divide(5.0, 0));

В представленном коде создали исключение RuntimeException. После этого определили условия вызова исключения — вызов метода деления на 0. В следующем тесте выполняется проверка метода divide. При делении на 0 вызывается исключение.

@Test public void testDevide() < when(mcalc.divide(15.0, 3)).thenReturn(5.0); assertEquals(calc.divide(15.0, 3), 5.0, 0); // проверка вызова метода verify(mcalc).divide(15.0, 3); // создаем исключение RuntimeException exception = new RuntimeException ("Division by zero"); // определяем поведение doThrow(exception).when(mcalc).divide(15.0, 0); assertEquals(calc.divide(15.0, 0), 0.0, 0); verify(mcalc).divide(15.0, 0); >

4. Использование интерфейса org.mockito.stubbing.Answer

Иногда описание поведения mock объекта требует определенной проверки с усложнением логики. В этом случае можно использовать интерфейс Answer , который позволяет реализовать заглушки методов со сложным поведением. В следующем тесте testThenAnswer при вызове метода сложения с определенными параметрами mcalc.add(11.0, 12.0) будет вызван метод answer, который подготовит ответ. Параметр InvocationOnMock позволяет получить информацию о вызываемом методе и параметрах.

// метод обработки ответа private Answer answer = new Answer() < @Override public Double answer(InvocationOnMock invocation) throws Throwable < // получение объекта mock Object mock = invocation.getMock(); System.out.println ("mock object : " + mock.toString()); // аргументы метода, переданные mock Object[] args = invocation.getArguments(); double d1 = (double) args[0]; double d2 = (double) args[1]; double d3 = d1 + d2; System.out.println ("" + d1 + " + " + d2); return d3; >>; @Test public void testThenAnswer() < // определение поведения mock для метода с параметрами when(mcalc.add(11.0, 12.0)).thenAnswer(answer); assertEquals(calc.add(11.0,12.0), 23.0, 0); >

5. Использование шпиона spy на реальных объектах

Mockito позволяет подключать к реальным объектам «шпиона» spy, контролировать возвращаемые методами значения и отслеживать количество вызовов методов. В следующем тесте создадим шпиона scalc, который подключим к реальному калькулятору и будем вызывать метод double15(). Необходимо отметить, что метод реального объекта double15 должен вернуть значение 15. Однако Mockito позволяет переопределить значение и согласно вновь назначенному условию новое значение должно быть 23.

В результате выполнения теста видим, что метод возвращает значение 23. Таким образом, фреймворк Mockito в сочетании с JUnit можно использовать для тестов реального класса. При этом, можно проверить, вызывался ли метод, сколько раз и с какими параметрами. Кроме этого, можно создавать заглушки только для некоторых методов. Это позволяет проверить поведение одних методов, используя заглушки для других.

6. Проверка вызова метода с задержкой, timeout

Фреймворк Mockito позволяет выполнить проверку вызова определенного метода в течение заданного в timeout времени. Задержка времени определяется в милисекундах.

@Test public void testTimout() < // определение результирующего значения mock для метода when(mcalc.add(11.0, 12.0)).thenReturn(23.0); // проверка значения assertEquals(calc.add(11.0,12.0), 23.0, 0); // проверка вызова метода в течение 10 мс verify(mcalc, timeout(100)).add(11.0, 12.0); >

7. Использование в тестах java классов

В следующем тесте при создании mock объектов используются java классы Iterator и Comparable. После этого определяются условия проверок и выполняются тесты.

@Test public void testJavaClasses() < // создание объекта mock Iteratormis = mock(Iterator.class); // формирование ответов when(mis.next()).thenReturn("Привет").thenReturn("Mockito"); // формируем строку из ответов String result = mis.next() + ", " + mis.next(); // проверяем assertEquals("Привет, Mockito", result); Comparable mcs = mock(Comparable.class); when(mcs.compareTo("Mockito")).thenReturn(1); assertEquals(1, mcs.compareTo("Mockito")); Comparable mci = mock(Comparable.class); when(mci.compareTo(anyInt())).thenReturn(1); assertEquals(1, mci.compareTo(5)); >

Скачать исходный код

Исходный код рассмотренного примера использования фреймворка Mockito с JUnit при модульном тестировании приложений в виде проекта Eclipse можно скачать здесь (12.4 Кб).

Источник

Оцените статью