Введение в EasyMock

Введение в EasyMock

1. Вступление

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

В этом руководстве мы познакомим вас с еще одним издевательским инструментом -EasyMock.

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

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


    org.easymock
    easymock
    3.5.1
    test

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

3. Основные понятия

При создании макетаwe can simulate the target object, specify its behavior, and finally verify whether it’s used as expected.

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

  1. создавая макет целевого класса

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

  3. использование насмешек в тестах

  4. проверка того, ведет ли он себя так, как ожидалось

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

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

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

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

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

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

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

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

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

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

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

public class exampleReader {

    private ArticleReader articleReader;
    private IArticleWriter articleWriter;

    // constructors

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

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

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

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

Затем мы посмеемся над ними, чтобы проверить поведениеexampleReader.

5. Макет с Java-кодом

Начнем с издевательства надArticleReader.

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

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

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

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

    exampleReader.readNext();

    verify(mockArticleReader);
}

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

Although we really don’t care what mockArticleReader.next() returns, we still need to specify a return value for mockArticleReader.next() с помощьюexpect(…).andReturn(…).

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

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

mockArticleReader.next();
replay(mockArticleReader);

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

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

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

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

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

@Test
public void whenReadNextAndSkimTopics_thenAllAllowed(){
    ArticleReader mockArticleReader
      = strictMock(ArticleReader.class);
    exampleReade exampleReader
      = new exampleReader(mockArticleReader);

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

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

    verify(mockArticleReader);
}

В этом фрагменте мыuse strictMock(…) to check the order of method calls. Для моков, созданныхmock(…) иstrictMock(…), любые неожиданные вызовы методов вызовутAssertionError.

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

@Test
public void whenReadNextAndOthers_thenAllowed(){
    ArticleReader mockArticleReader = niceMock(ArticleReader.class);
    exampleReade exampleReader = new exampleReader(mockArticleReader);

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

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

    verify(mockArticleReader);
}

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

5.3. Издевательские броскиException

Теперь давайте продолжим насмешку над интерфейсомIArticleWriter и обработкой ожидаемогоThrowables:

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

    expect(mockArticleWriter
      .write("easymock",""))
      .andThrow(new IllegalArgumentException());
    replay(mockArticleWriter);

    // write malicious content and capture exception as expectedException

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

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

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

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

EasyMock также поддерживает инъекции макетов с использованием аннотаций. To use them, we need to run our unit tests with EasyMockRunner so that it processes @Mock and @TestSubject annotations.с

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

@RunWith(EasyMockRunner.class)
public class exampleReaderAnnotatedTest {

    @Mock
    ArticleReader mockArticleReader;

    @TestSubject
    exampleReader exampleReader = new exampleReader();

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

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

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

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

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

public class exampleReaderAnnotatedWithRuleTest {

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

    //...

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

}

7. Мок сEasyMockSupport

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

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

Это безобразно, и нам нужно элегантное решение.

К счастью, у нас есть классEasyMockSupport in EasyMock, который поможет с этим справиться. Этоhelps keep track of mocks, such that we can replay and verify them in такая партия:

//...
public class exampleReaderMockSupportTest 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(…) в фазуexpect. Это помогает определить, сколько раз мы ожидаем, что метод будет вызван, чтобы избежать дублирования кода.

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

EasyMockSupport easyMockSupport = new EasyMockSupport();

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

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

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

    easyMockSupport.verifyAll();
}

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

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

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

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

Если вам интересно, посмотритеthis article для сравнения EasyMock, Mocket и JMockit.

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