410013796724260
• Webmoney
R335386147728
Z369087728698
JUnit и фреймворк MockitoРазработчикам программного обеспечения на разных этапах своей деятельности приходится сталкиваться с тремя стратегиями тестирования : функциональным, интеграционным и модульным тестированием. Все они используются для тестирования приложений разными способами, и каждая из стратегий имеет определенную цель. К сожалению, ни одна из стратегий не даёт стопроцентной гарантии обнаружения всех имеющихся ошибок. И даже комбинация всех трёх стратегий не может дать такой гарантии. Но их сочетание позволяет существенно снизить количество ошибок и убедить разработчика, что приложение функционирует согласно предъявленным требованиям. Функциональное тестированиеПроведение функционального тестирования, как правило, связано с созданием специальной группы специалистов, занимающихся тестированием. На этом этапе приложение развертывается в действующем окружении и проверяется его соответствие ТЗ (Техническому Заданию) и предъявленным функциональным требованиям. Команда тестеровщиков использует комплекс автоматизированных и ручных тестов. Автоматизировать процесс функционального тестирования можно, если приложение включает API (Application Programming Interface) - интерфейс прикладного программирования, на котором оно построено. Однако наличие интерфейса в приложении (desktop, web) существенно снижает возможности полной автоматизации данного процесса. Интеграционное тестированиеСтратегия интеграционного тестирования основывается на проверке прикладного кода в окружении, близком к фактическому окружению, но не являющимся им. Главная цель данной стратегии – убедиться в правильности взаимодействия кода с внешними ресурсами и взаимодействия различных технологий в приложении между собой. В интеграционном тестировании не требуется использовать фиктивные данные, как при модульном тестировании. Вместо этого в интеграционных тестах часто используются базы данных, находящиеся в памяти, которые легко можно создавать и уничтожать во время выполнения тестов. База данных в памяти – это самая настоящая база данных, что дает возможность проверить правильность работы сущностей JPA. Но все же эта база данных не совсем настоящая – она лишь имитирует настоящую базу данных для целей интеграционного тестирования. Модульное тестированиеЦелью модульного тестирования является проверка работы прикладной логики всего приложения или отдельных его частей при разных исходных данных, и анализ правильности получаемых результатов. Несмотря на то, что цель модульного тестирования выглядит простой и понятной, реализация этого типа тестирования может оказаться очень сложным и запутанным делом, особенно при наличии «старого» кода. Основные приемы проведения модульного тестирования опираются на следующие базовые принципы :
При проведении модульного тестирования для создания фиктивных классов-зависимостей можно использовать простой, но мощный фреймворк Mockito совместно с JUnit. Фреймворк MockitoФреймворк Mockito предоставляет ряд возможностей для создания заглушек вместо реальных классов или интерфейсов при написании JUnit тестов. Mockito можно скачать с сайта https://code.google.com/p/mockito, либо определить в зависимостях (dependencies) в maven проекте : <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>1.9.5</version> <scope>test</scope> </dependency> Наибольшее распространение получили следующие возможности Mockito :
Синтаксис создания заглушки 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 { ICalculator mcalc = Mockito.mock(ICalculator.class); } Если использовать статический импорт Mockito, то синтаксис будет иметь следующий вид : import static org.mockito.Mockito.*; import com.example ICalculator; public class Test_Mockito { ICalculator mcalc = mock(ICalculator.class); } ПРИМЕЧАНИЕ : помните, что методы 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 предоставляет следующие методы :
Следующий тест демонстрирует контроль количества вызовов метода 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<T>Иногда описание поведения mock объекта требует определенной проверки с усложнением логики. В этом случае можно использовать интерфейс Answer<T>, который позволяет реализовать заглушки методов со сложным поведением. В следующем тесте testThenAnswer при вызове метода сложения с определенными параметрами mcalc.add(11.0, 12.0) будет вызван метод answer, который подготовит ответ. Параметр InvocationOnMock позволяет получить информацию о вызываемом методе и параметрах. // метод обработки ответа private Answer<Double> answer = new Answer<Double>() { @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. @Test public void testSpy() { Calculator scalc = spy(new Calculator()); when(scalc.double15()).thenReturn(23.0); // вызов метода реального класса scalc.double15(); // проверка вызова метода verify(scalc).double15(); // проверка возвращаемого методом значения assertEquals(23.0, scalc.double15(), 0); // проверка вызова метода не менее 2-х раз verify(scalc, atLeast(2)).double15(); } В результате выполнения теста видим, что метод возвращает значение 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 Iterator<String> mis = mock(Iterator.class); // формирование ответов when(mis.next()).thenReturn("Привет").thenReturn("Mockito"); // формируем строку из ответов String result = mis.next() + ", " + mis.next(); // проверяем assertEquals("Привет, Mockito", result); Comparable<String> mcs = mock(Comparable.class); when(mcs.compareTo("Mockito")).thenReturn(1); assertEquals(1, mcs.compareTo("Mockito")); Comparable<Integer> mci = mock(Comparable.class); when(mci.compareTo(anyInt())).thenReturn(1); assertEquals(1, mci.compareTo(5)); } Скачать исходный кодИсходный код рассмотренного примера использования фреймворка Mockito с JUnit при модульном тестировании приложений в виде проекта Eclipse можно скачать здесь (12.4 Кб). |