Перейти к основному содержимому

Введение в EasyMock

· 7 мин. чтения

1. Введение

В прошлом мы много говорили о JMockit и Mockito .

В этом уроке мы познакомим вас с другим инструментом для мокаинга — EasyMock .

2. Зависимости Maven

Прежде чем мы углубимся, давайте добавим следующую зависимость в наш pom.xml :

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

Последнюю версию всегда можно найти здесь .

3. Основные концепции

При создании макета мы можем имитировать целевой объект, указать его поведение и, наконец, проверить, используется ли он должным образом.

Работа с макетами EasyMock включает четыре шага:

  1. создание макета целевого класса
  2. запись его ожидаемого поведения, включая действие, результат, исключения и т. д.
  3. использование моков в тестах
  4. проверка, ведет ли он себя так, как ожидалось

После завершения записи мы переключаем ее в режим «воспроизведения», чтобы макет вел себя так, как записан, при сотрудничестве с любым объектом, который будет его использовать.

В конце концов, мы проверяем, все ли идет так, как ожидалось.

Четыре упомянутых выше шага относятся к методам в org.easymock.EasyMock :

  1. mock(…) : создает макет целевого класса, будь то конкретный класс или интерфейс. После создания макет находится в режиме «записи», что означает, что EasyMock запишет любое действие, которое выполняет макет объекта, и воспроизведет их в режиме «воспроизведения».

  2. expect(…) : с помощью этого метода мы можем установить ожидания, включая вызовы, результаты и исключения, для связанных действий записи.

  3. replay(…) : переключает данный макет в режим «воспроизведения». Затем любое действие, запускающее ранее записанные вызовы методов, будет воспроизводить «записанные результаты».

  4. Verify(…) : проверяет, что все ожидания оправдались и не было выполнено никаких неожиданных вызовов на макете. **

    **

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

4. Практический пример мокинга

Прежде чем мы продолжим, давайте взглянем на пример контекста: скажем, у нас есть читатель блога ForEach, которому нравится просматривать статьи на веб-сайте, а затем он/она пытается писать статьи.

Начнем с создания следующей модели:

public class ForEachReader {

private ArticleReader articleReader;
private IArticleWriter articleWriter;

// constructors

public ForEachArticle readNext(){
return articleReader.next();
}

public List<ForEachArticle> readTopic(String topic){
return articleReader.ofTopic(topic);
}

public String write(String title, String content){
return articleWriter.write(title, content);
}
}

В этой модели у нас есть два закрытых члена: articleReader (конкретный класс) и articleWriter (интерфейс).

Затем мы создадим им макет, чтобы проверить поведение ForEachReader .

5. Мок с Java-кодом

Давайте начнем с создания объекта ArticleReader .

5.1. Типичное издевательство

Мы ожидаем, что метод articleReader.next() будет вызываться, когда читатель пропускает статью:

@Test
public void whenReadNext_thenNextArticleRead(){
ArticleReader mockArticleReader = mock(ArticleReader.class);
ForEachReader foreachReader
= new ForEachReader(mockArticleReader);

expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);

foreachReader.readNext();

verify(mockArticleReader);
}

В приведенном выше примере кода мы строго придерживаемся четырехэтапной процедуры и моделируем класс ArticleReader .

Хотя нас действительно не волнует, что возвращает mockArticleReader.next() , нам все же нужно указать возвращаемое значение для mockArticleReader.next() с помощью expect(…).andReturn(…).

С expect(…) EasyMock ожидает, что метод вернет значение или выдаст исключение.

Если мы просто сделаем:

mockArticleReader.next();
replay(mockArticleReader);

EasyMock будет жаловаться на это, так как требует вызова expect(…).andReturn(…) если метод что-то возвращает.

Если это метод void , мы можем ожидать его действия с помощью expectLastCall() следующим образом:

mockArticleReader.someVoidMethod();
expectLastCall();
replay(mockArticleReader);

5.2. Порядок воспроизведения

Если нам нужно, чтобы действия воспроизводились в определенном порядке, мы можем быть более строгими:

@Test
public void whenReadNextAndSkimTopics_thenAllAllowed(){
ArticleReader mockArticleReader
= strictMock(ArticleReader.class);
ForEachReade foreachReader
= new ForEachReader(mockArticleReader);

expect(mockArticleReader.next()).andReturn(null);
expect(mockArticleReader.ofTopic("easymock")).andReturn(null);
replay(mockArticleReader);

foreachReader.readNext();
foreachReader.readTopic("easymock");

verify(mockArticleReader);
}

В этом фрагменте мы используем strictMock(…) для проверки порядка вызовов методов . Для макетов, созданных mock(…) и strictMock(…) , любые неожиданные вызовы методов вызовут AssertionError .

Чтобы разрешить любой вызов метода для макета, мы можем использовать niceMock(…) :

@Test
public void whenReadNextAndOthers_thenAllowed(){
ArticleReader mockArticleReader = niceMock(ArticleReader.class);
ForEachReade foreachReader = new ForEachReader(mockArticleReader);

expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);

foreachReader.readNext();
foreachReader.readTopic("easymock");

verify(mockArticleReader);
}

Здесь мы не ожидали вызова foreachReader.readTopic(…) , но EasyMock не будет жаловаться. С niceMock(…) EasyMock теперь заботится только о том, выполнил ли целевой объект ожидаемое действие или нет.

5.3. Имитация исключений _

Теперь давайте продолжим издеваться над интерфейсом IArticleWriter и как обрабатывать ожидаемые Throwables :

@Test
public void whenWriteMaliciousContent_thenArgumentIllegal() {
// mocking and initialization

expect(mockArticleWriter
.write("easymock","<body onload=alert('foreach')>"))
.andThrow(new IllegalArgumentException());
replay(mockArticleWriter);

// write malicious content and capture exception as expectedException

verify(mockArticleWriter);
assertEquals(
IllegalArgumentException.class,
expectedException.getClass());
}

В приведенном выше фрагменте мы ожидаем, что articleWriter достаточно надежен, чтобы обнаруживать атаки XSS (межсайтовый скриптинг) .

Поэтому, когда читатель пытается внедрить вредоносный код в содержимое статьи, автор должен выдать исключение IllegalArgumentException . Мы записали это ожидаемое поведение, используя expect(…).andThrow(…) .

6. Макет с аннотацией

EasyMock также поддерживает внедрение макетов с помощью аннотаций. Чтобы использовать их, нам нужно запустить наши модульные тесты с помощью EasyMockRunner , чтобы он обрабатывал аннотации @Mock и @TestSubject .

Перепишем предыдущие фрагменты:

@RunWith(EasyMockRunner.class)
public class ForEachReaderAnnotatedTest {

@Mock
ArticleReader mockArticleReader;

@TestSubject
ForEachReader foreachReader = new ForEachReader();

@Test
public void whenReadNext_thenNextArticleRead() {
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
foreachReader.readNext();
verify(mockArticleReader);
}
}

Эквивалент mock(…) , макет будет вставлен в поля, аннотированные с помощью @Mock . И эти моки будут внедряться в поля класса, аннотированные @TestSubject .

В приведенном выше фрагменте мы явно не инициализировали поле articleReader в foreachReader. При вызове foreachReader.readNext() мы можем интерпретировать неявно вызываемый mockArticleReader .

Это произошло потому , что mockArticleReader был внедрен в поле articleReader .

Обратите внимание, что если мы хотим использовать другое средство запуска тестов вместо EasyMockRunner , мы можем использовать тестовое правило JUnit EasyMockRule :

public class ForEachReaderAnnotatedWithRuleTest {

@Rule
public EasyMockRule mockRule = new EasyMockRule(this);

//...

@Test
public void whenReadNext_thenNextArticleRead(){
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
foreachReader.readNext();
verify(mockArticleReader);
}

}

7. Мок с EasyMockSupport

Иногда нам нужно ввести несколько моков в одном тесте, и нам приходится повторять вручную:

replay(A);
replay(B);
replay(C);
//...
verify(A);
verify(B);
verify(C);

Это некрасиво, и нам нужно элегантное решение.

К счастью, у нас есть класс EasyMockSupport в EasyMock , который поможет справиться с этим. Это помогает отслеживать макеты, чтобы мы могли воспроизвести и проверить их в пакете, подобном этому:

//...
public class ForEachReaderMockSupportTest extends EasyMockSupport{

//...

@Test
public void whenReadAndWriteSequencially_thenWorks(){
expect(mockArticleReader.next()).andReturn(null)
.times(2).andThrow(new NoSuchElementException());
expect(mockArticleWriter.write("title", "content"))
.andReturn("BAEL-201801");
replayAll();

// execute read and write operations consecutively

verifyAll();

assertEquals(
NoSuchElementException.class,
expectedException.getClass());
assertEquals("BAEL-201801", articleId);
}

}

Здесь мы имитировали как articleReader , так и articleWriter . При установке этих макетов в режим «воспроизведения» мы использовали статический метод replayAll() , предоставляемый EasyMockSupport , и использовали verifyAll() для проверки их поведения в пакетном режиме.

Мы также ввели метод times(…) на этапе ожидания . Это помогает указать, сколько раз мы ожидаем, что метод будет вызываться, чтобы мы могли избежать дублирования кода.

Мы также можем использовать EasyMockSupport через делегирование:

EasyMockSupport easyMockSupport = new EasyMockSupport();

@Test
public void whenReadAndWriteSequencially_thenWorks(){
ArticleReader mockArticleReader = easyMockSupport
.createMock(ArticleReader.class);
IArticleWriter mockArticleWriter = easyMockSupport
.createMock(IArticleWriter.class);
ForEachReader foreachReader = new ForEachReader(
mockArticleReader, mockArticleWriter);

expect(mockArticleReader.next()).andReturn(null);
expect(mockArticleWriter.write("title", "content"))
.andReturn("");
easyMockSupport.replayAll();

foreachReader.readNext();
foreachReader.write("title", "content");

easyMockSupport.verifyAll();
}

Раньше мы использовали статические методы или аннотации для создания макетов и управления ими. Под капотом эти статические и аннотированные макеты контролируются глобальным экземпляром EasyMockSupport .

Здесь мы явно создали его экземпляр и взяли все эти моки под свой собственный контроль через делегирование. Это может помочь избежать путаницы, если в нашем тестовом коде с EasyMock возникнут конфликты имен или возникнут какие-либо подобные случаи.

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

В этой статье мы кратко представили основы использования EasyMock, о том, как создавать фиктивные объекты, записывать и воспроизводить их поведение, а также проверять правильность их поведения.

Если вам интересно, ознакомьтесь с этой статьей для сравнения EasyMock, Mocket и JMockit.

Как всегда, полную реализацию можно найти на Github .