Начало работы с EasyMock и JUnit

Научитесь использовать EasyMock для создания тестовых макетов, записи и воспроизведения ожиданий и проверки вызовов методов на макетированных экземплярах. Мы настроим EasyMock с JUnit 4 и JUnit 5, оба.

1. Зависимости EasyMock

Включите в проект последнюю версию easymock из репозитория Maven.

<dependency><groupId>org.easymock</groupId><artifactId>easymock</artifactId><version>4.3</version><scope>test</scope></dependency>

2. Тестовые шаги с EasyMock

Каркас EasyMock создает объекты-заглушки с помощью объекта java.lang.reflect.Proxy. Когда мы создаем объект-заглушку во время выполнения теста, объект-прокси занимает место реального объекта. Объект-прокси получает свои поля и методы из интерфейса или класса, которые мы передаем при создании заглушки.

Типичное тестирование с помощью EasyMock состоит из четырех этапов: создание макета, ожидание, воспроизведение и проверка.

Начало работы с EasyMock и JUnit0
  • Создать Mock: Используйте EasyMock.mock() для создания макетов целевых классов, поведение которых мы хотим делегировать прокси-объектам. Обычно мы создаем макеты классов, которые взаимодействуют с внешними системами, или классов, которые не должны быть частью тестового кода.
  • Запись ожиданий: используйте EasyMock.expect() для записи ожиданий от фиктивных объектов. Эти ожидания включают имитацию метода с определенными аргументами, возвращаемое значение вызванного метода и количество раз, которое метод должен быть вызван.
  • Replay: Метод EasyMock.replay() делает объект Mock доступным. В режиме 'replay', когда тест вызывает записанный метод, то mock вернет записанные результаты на предыдущем шаге.
  • Проверка: EasyMock.verify() проверяет, что во время выполнения теста все ожидания были выполнены так, как записано, и что на имитаторе не было выполнено никаких неожиданных вызовов.

Мы увидим, как выполнить все эти шаги, в разделе 4.

3. Настройка EasyMock с помощью JUnit

Прежде чем двигаться дальше, важно усвоить, что нам необходимо использовать разные подходы для запуска тестов в зависимости от базовой версии JUnit — 4 или 5. Поэтому вы можете выбрать одно из следующих решений в соответствии с требованиями вашего проекта.

Следующие решения используются для обработки аннотаций @Mock и @TestSubject в тестовом классе. Если мы не используем эти аннотации, то мы можем пропустить использование следующих решений.

3.1 С JUnit 4

Устаревший JUnit 4 использует класс EasyMockRunner для запуска тестов. Обратите внимание, что этот раннер работает только с JUnit 4.5 или выше.

@RunWith(EasyMockRunner.class)public class EasyMockTests {}

В JUnit 4 мы также можем использовать EasyMockRule вместо EasyMockRunner с тем же эффектом.

public class EasyMockTests {@Rulepublic EasyMockRule mockRule = new EasyMockRule(this);}

3.2 С JUnit 5

В JUnit 5 правила больше не могут использоваться. Новый JUnit 5 использует класс EasyMockExtension для запуска тестов. Начиная с EasyMock 4.1, EasyMock поставляется с этим расширением JUnit 5 из коробки.

@ExtendWith(EasyMockExtension.class)public class EasyMockTests {}

4. Демо EasyMock

Давайте разберем все шаги в easymock на примере. Сначала мы сделаем несколько классов и зависимостей для имитации, затем напишем для них тест.

4.1 Тестируемая система

У нас есть класс RecordService, который можно использовать для сохранения данных Record в бэкэнд-базе данных. RecordService зависит от RecordDao для взаимодействия с базой данных и SequenceGenerator для получения следующего допустимого порядкового номера, используемого в качестве идентификатора записи.

 @Данные@NoArgsConstructorпубличный класс Запись {публичная запись(имя строки) {это.имя = имя;}частный длинный идентификатор;личное имя строки;} 
 @Бревнооткрытый класс SequenceGenerator {частное длинное значение = 1;публичный длинный getNext() {log.info("Получить следующий идентификатор в SequenceGenerator");возвращаемое значение++;}}
 @Бревнопубличный класс RecordDao {публичная запись saveRecord(запись записи) {log.info("Сохранение записи в RecordDao");запись о возврате;}}
 @Бревнопубличный класс RecordService {частный финал RecordDao dao;частный конечный генератор SequenceGenerator;public RecordService(генератор SequenceGenerator, RecordDao dao) {этот.генератор = генератор;это.дао = дао;}публичная запись saveRecord(запись записи) {log.info("Сохранение записи в RecordService");запись.setId(генератор.getNext());вернуть dao.saveRecord(запись);}}

4.2 Простой тест

В данном тесте мы тестируем метод RecordService.saveRecord(). Служба зависит от RecordDao и SequenceGenerator. Dao взаимодействует с базой данных, а генератор последовательностей также взаимодействует с базой данных для извлечения следующего идентификатора записи. Нам нужно имитировать обе зависимости, поскольку они выходят за рамки этого тестового случая.

//Prepare mocksRecordDao mockDao = EasyMock.mock(RecordDao.class);SequenceGenerator mockGenerator = EasyMock.mock(SequenceGenerator.class);

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

Мы можем использовать гибкие сопоставители, такие как anyObject(), isA(), notNull() и т. д., чтобы записывать ожидания, соответствующие ряду аргументов. Но мы должны возвращать конкретное значение из сопоставителей результатов, таких как методы andReturn() или andThrow().

Количество вызовов указывается с помощью once(), times(exactCount), times(min, max), atLeastOnce() и anyTimes().

Record record = new Record();record.setName("Test Record");expect(mockGenerator.getNext()).andReturn(100L).once();expect(mockDao.saveRecord(EasyMock.anyObject(Record.class))).andReturn(record).once()

Чтобы перевести выполнение теста в режим воспроизведения, мы можем использовать воспроизведение имитаторов либо по одному, либо объединить все имитаторы в один вызов воспроизведения.

replay(mockGenerator, mockDao);//orreplay(mockGenerator);replay(mockDao);

Если мы не хотим отслеживать все имитаторы в тесте, мы можем использовать EasyMockSupport для одновременного воспроизведения всех имитаторов.

public class MockEasyTests {EasyMockSupport support = new EasyMockSupport();@Testpublic void test() {//...support.replayAll();//...}}

В режиме воспроизведения мы выполняем операцию в тестируемой системе. Это вызовет записанные методы в ожиданиях и вернет значения из фиктивных объектов.

Наконец, мы проверяем фиктивные объекты, чтобы все ожидания были выполнены и не было никаких неожиданных вызовов на фиктивных объектах. Синтаксис verify() похож на метод replay(). Используйте один из следующих вариантов для запуска проверки фиктивных объектов.

verify(mockGenerator, mockDao);//orverify(mockGenerator);verify(mockDao);//orEasyMockSupport support = new EasyMockSupport();support.verifyAll();

Полный пример тестового случая, включающий все вышеперечисленные шаги, выглядит следующим образом:

public class EasyMockTests {@Testpublic void whenSaveCorrectRecord_ItSavedSuccessfully() {//Prepare mocksRecordDao mockDao = EasyMock.mock(RecordDao.class);SequenceGenerator mockGenerator = EasyMock.mock(SequenceGenerator.class);Record record = new Record();record.setName("Test Record");//Set expectations//expect(mockGenerator.getNext()).andReturn(100L).once();mockGenerator.getNext();expectLastCall().andReturn((long) 100);expect(mockDao.saveRecord(EasyMock.anyObject(Record.class))).andReturn(record).once();//Replayreplay(mockGenerator, mockDao);//Test and assertionsRecordService service = new RecordService(mockGenerator, mockDao);Record savedRecord = service.saveRecord(record);assertEquals("Test Record", savedRecord.getName());assertEquals(100L, savedRecord.getId());//Verifyverify(mockGenerator, mockDao);}}

4.3 Тест с использованием аннотаций

Предыдущий пример напрямую использует метод mock() для создания моков, а затем внедряет моки в класс RecordService. Мы можем использовать аннотации @Mock и @TestSubject, чтобы сделать это декларативно.

Обратите внимание, что все остальные шаги, т.е. запись ожиданий, воспроизведение и проверка, не меняются. Это изменение затрагивает только mocking.

@ExtendWith(EasyMockExtension.class)public class EasyMockTestsWithAnnotationsJUnit5 {@MockRecordDao mockDao;@MockSequenceGenerator mockGenerator;@TestSubjectRecordService service = new RecordService(mockGenerator, mockDao);@Testpublic void whenSaveCorrectRecord_ItSavedSuccessfully() {//test code}}

4.4 Тест с использованием EasyMockSupport

Помимо создания экземпляра EasyMockSupport, мы можем расширить тестовый класс из EasyMockSupport. Таким образом, мы можем напрямую получить доступ к методам replayAll() и verifyAll().

@ExtendWith(EasyMockExtension.class)public class EasyMockTestsWithEasyMockSupport extends EasyMockSupport {@Testpublic void whenSaveCorrectRecord_ItSavedSuccessfully() {//create mock//record expecationsreplayAll();//test operationverifyAll();}}

5. Предварительные концепции

5.1. Насмешка против строгой насмешки против хорошей насмешки

EasyMock поддерживает три типа фиктивных объектов. Используйте следующие методы для создания фиктивных объектов:

  • EasyMock.mock()
  • EasyMock.strictMock()
  • EasyMock.niceMock()

Мы также можем использовать метод EasyMock.createMock() для создания следующих макетов:

//Default MockEasyMock.createMock(RecordDao.class);//---or---EasyMock.createMock(MockType.DEFAULT, RecordDao.class);//Nice MockEasyMock.createMock(MockType.NICE, RecordDao.class);//Strict MockEasyMock.createMock(MockType.STRICT, RecordDao.class);

Поведение этих макетов отличается при проверке зафиксированных ожиданий.

  • Default Mock: Тест не пройден, если вызывается не ожидаемый метод или если не вызывается ожидаемый метод. Порядок вызовов методов не имеет значения.
  • Nice Mock: Тест проваливается, если метод ожидается, но не вызывается. Методы, которые вызываются, но не ожидаются, возвращаются с соответствующим типу значением по умолчанию(0, null или false). Порядок вызовов методов не имеет значения.
  • Строгий фиктивный объект: аналогичен фиктивному объекту по умолчанию, за исключением того, что порядок вызовов методов имеет значение.

Обратите внимание, что для имитаторов, созданных с помощью mock() и strictMock(), любой неожиданный вызов метода приведет к возникновению AssertionError.

niceMock() допускает любые неожиданные вызовы методов на имитаторе без провала теста, если метод возвращает соответствующее типу значение по умолчанию.

5.2. Исключения-фиктивные исключения

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

Используйте метод andThrow() для записи ожидания класса исключения.

EasyMock.expect(...).andThrow(new IOException());

6. Заключение

В этом руководстве EasyMock мы научились настраивать easymock с Junit и выполнять тесты на платформах junit 4 и junit 5. Мы изучили основные концепции тестирования с помощью easymock, включая такие этапы тестирования, как mock, expect, replay и verify.

Наконец, мы научились писать полноценный тест с примером.

Исходный код на Github

Прокрутить вверх