Статьи

Начало работы с Mockito

Эта статья является частью нашего курса Академии под названием « Тестирование с помощью Mockito» .

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

В этом руководстве мы рассмотрим Mockito Mocking Framework и подготовим проект Eclipse для его использования, добавив его в путь к классам.

1. Почему издеваться?

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

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

Это когда все наши взаимозависимости становятся проблемой. Мы можем в конечном итоге выполнить код в другом классе, в котором есть ошибка, которая приводит к сбою нашего модульного теста. Представьте себе класс, который мы используем для чтения пользовательских данных из базы данных, что произойдет, если базы данных не будет, когда мы захотим запустить наши модульные тесты? Представьте себе класс, который вызывает несколько удаленных веб-сервисов, что, если они не работают или требуют много времени для ответа? Наши модульные тесты могут не работать из-за наших зависимостей, а не из-за некоторых проблем с поведением нашего кода. Это нежелательно.

В дополнение к этому может быть очень трудно принудительно вызвать конкретное событие или условие ошибки, которое мы хотим гарантировать, что наш код обрабатывает правильно. Что если мы хотим проверить, что некоторый класс, который десериализует объект, правильно обрабатывает возможное исключение ObjectStreamException? Что если мы хотим протестировать все возвращаемые значения границы от соавтора? Как насчет того, чтобы некоторая вычисленная величина была правильно передана сотруднику? Может потребоваться много кода и много времени, чтобы воспроизвести условия для наших тестов, если это вообще возможно.

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

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

2. Введение в Mockito Framework

Mockito — это Mocking Framework, который позволяет очень легко создавать макеты для классов и интерфейсов, с которыми взаимодействует ваш тестируемый класс. Mockito предоставляет очень простой API для создания макетов и определения их поведения. Это позволяет вам очень быстро указать ожидаемое поведение и проверить взаимодействие с вашими макетами.

У Mockito, по сути, две фазы, одна или обе из которых выполняются как часть модульных тестов:

  • Stubbing
  • верификация

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

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

Соединяя эти две простые фазы, мы можем создавать чрезвычайно гибкие и мощные модульные тесты, кодирующие сложное поведение имитация и сложную проверку взаимодействия имитацией с очень простым API-интерфейсом Mockito.

Однако у Mockito есть некоторые ограничения, в том числе

  • Вы не можете издеваться над финальными классами
  • Вы не можете издеваться над статическими методами
  • Вы не можете издеваться над финальными методами
  • Вы не можете смоделировать equals () или hashCode ()

2.1. Быстрый пример заглушки

Представьте, что вы пишете класс, который вызывает API физического датчика температуры. Вы хотите вызвать double getDegreesC() и вернуть одну из следующих строк — «Горячая», «Мягкая», «Холодная» — на основе значения, возвращаемого датчиком. Было бы очень трудно, если не сказать больше, чтобы ваши юнит-тесты контролировали температуру в помещении, чтобы проверить ваши функциональные возможности. Но что, если мы используем Mockito для создания макета, который мы заменяем датчиком?

Теперь мы можем написать такой код в нашем модульном тесте:

1
when(sensor.getDegreesC()).thenReturn(15.0);

Это говорит Mockito, что когда датчик mock получает вызов getDegreesC() он должен вернуть значение 15.0.

2.2. Быстрый пример проверки

Представьте, что у вас есть класс, который выполняет некоторые вычисления и отвечает за уведомление наблюдателя о завершении его расчета. Вы хотите убедиться, что метод notify() наблюдателя был вызван один раз как часть выполнения вашего метода. Вы можете установить некоторое логическое значение в наблюдателе и проверить его из своего модульного теста, но это означает изменение некоторого производственного кода, кода, который вы, возможно, даже не имеете. Что насчет Мокито, что если наблюдатель — это издевательство?

Теперь мы можем написать такой код в нашем модульном тесте:

1
verify(observer).notify();

Это говорит Mockito, что метод notify notify() должен вызываться один раз и только один раз, в противном случае модульный тест не пройдёт.

3. Смешивание Мокито

Теперь мы немного узнали о фреймворке, давайте использовать его в проекте.

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

1
2
3
4
5
6
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
   </dependency>

Если вы используете Gradle, просто добавьте следующее

1
2
3
dependencies {
    testCompile "org.mockito:mockito-all:1.9.5"
}

Чтобы добавить Mockito в путь к классам проекта Eclipse, по старинке возьмите последнюю флягу со страницы загрузки Mockito (возьмите mockito-all-1.9.5.jar) и загрузите ее на свой жесткий диск.

Щелкните правой кнопкой мыши свой проект затмения и выберите «Свойства», а затем выберите «Путь сборки Java» на левой панели и «Библиотеки» справа.

На вкладке «Библиотеки» нажмите кнопку «Добавить внешние банки» и перейдите к ранее загруженному банку «mockito-all». Выберите банку, и теперь она добавлена ​​в ваш проект и доступна для использования.

На момент написания статьи последняя версия Mockito была 1.9.5, но вы должны проверить наличие обновлений, прежде чем добавлять ее в свой проект.

4. Использование Mockito с JUnit

Для интеграции Mockito в ваш тестовый класс JUnit вы можете использовать предоставленный Test Runner MockitoJUnitRunner . Просто аннотируйте свой тестовый класс с помощью:

1
@RunWith(MockitoJUnitRunner.class)

Это говорит Mockito, что нужно брать аннотированные макеты в тестовом классе и инициализировать их для насмешек. Затем вы можете просто аннотировать любую переменную экземпляра с помощью @Mock чтобы использовать ее как макет. Обратите внимание, что вы должны импортировать org.mockito.Mock а не org.mockito.mockitoannotations.Mock , что не рекомендуется.

Как и в случае с чем-либо, полный пример проясняет ситуацию, мы создадим новый класс Test, и в нем мы будем использовать Mockito для макета java.util.List :

01
02
03
04
05
06
07
08
09
10
11
12
import java.util.List;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
 
@RunWith(MockitoJUnitRunner.class)
public class MyTest {
     
    @Mock
    private List<String> mockList;
     
}

Аннотация @Mock сообщает Mockito, что mockList следует рассматривать как mock, а @RunWith(MockitoJUnitRunner.class) говорит Mockito, что нужно просмотреть все аннотированные элементы MyTest и инициализировать их для Mocking. Вам не нужно назначать новый экземпляр для mockList, это делается для вас под капотом Mockito. С этим простым кодом выше mockList готов к использованию в качестве макета.

Попробуйте добавить следующий импорт:

1
2
3
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Test;

А затем следующий простой тестовый пример:

1
2
3
4
5
6
7
8
9
@Test
public void test() {
 String expected = "Hello, World!";
 when(mockList.get(0)).thenReturn(expected);
 
 String actual = mockList.get(0);
 assertEquals(expected, actual);
 System.out.println(actual);
}

Здесь мы видим, что мы настроили ожидание — что у нас есть строка "Hello, World!" и затем мы продолжаем List.get() метод List.get() нашего фиктивного списка, чтобы вернуть ожидаемую строку при запросе первого элемента списка.

Затем мы вызываем mockList.get(0) чтобы получить наше действительное значение для тестирования и утверждаем, что наше действительное значение равно нашему ожидаемому значению, и выводим его на консоль для хорошей меры.

Ни в коем случае мы не создали реальный список и не вставили «Hello, World!» в список. Это всего лишь фиктивный список, и единственная функциональность, о которой он знает или знает, — это метод get () с вводом 0.

Попробуйте изменить String actual = mockList.get(0); String actual = mockList.get(1); и запустить тест. Вы увидите, что actual теперь равно нулю. Причина в том, что единственная функция, которую мы оформили, — это вызов .get () с вводом 0 — Mockito не знает, что делать с вводом 1, поэтому он просто возвращает ноль. Фактически любой другой метод List, который мы вызываем, вернет значение null, и любой метод, который ничего не возвращает, будет эффективно действовать как no-op. Это мощный контроль, в нескольких строках кода мы создали реализацию List, которая делает именно то, что мы хотим, когда мы хотим это каждый раз, когда она вызывается.

5. Лучшие практики Mockito

Mockito поощряет стандартную передовую практику в области модульного тестирования и проектирования в целом, а именно:

  • В Mockito нет условий для насмешки статических методов, потому что Mockito поощряет объектно-ориентированное проектирование и внедрение зависимостей поверх процедурного кода.
  • У Mockito нет условия для насмешки частных методов, потому что публичные методы должны быть черными ящиками, а с точки зрения тестирования частных методов не существует.
  • Mockito упаковывает и поощряет использование Hamcrest Matchers, что будет рассмотрено в последующих уроках.
  • Мокито поощряет соблюдение закона Деметры и не поощряет насмешливые цепные методы.
  • Вы не должны заглушать или проверять макет, который разделяется между различными потоками. Однако вы можете вызывать методы общего макета.
  • Вы не можете проверить метод toString() макета, потому что он может быть вызван самой тестовой средой, что делает проверку невозможной.
  • Если в ваших тестовых org.mockito.BDDMockito используется нотация Given When Then, вы можете использовать методы- org.mockito.BDDMockito из org.mockito.BDDMockito чтобы when(mock.method()).thenReturn(something) given(mock.method()).willReturn(something) как это будет приятно читать в вашем тестовом формате.
  • Можно использовать Mockito без аннотаций Mockito, однако гораздо проще и удобнее использовать аннотации, так что мы будем делать в этих уроках.
  • Вы можете «шпионить» за любым классом, включая тестируемый класс, если ваше тестирование требует, чтобы вы изменили поведение определенного метода класса для целей теста. Mockito явно рекомендует, чтобы шпионы использовались только осторожно и время от времени, например, когда они ограничены работой с унаследованным кодом. Это будет описано в следующем уроке.
  • В случае, если реальный вызов метода spied может вызвать состояние ошибки или не может быть вызван по какой-либо другой причине, Mockito рекомендует использовать семейство методов do * для создания заглушек. Это будет описано в следующем уроке.
  • Mockito позволит вам использовать сопоставители аргументов вместо реальных аргументов с тем ограничением, что, если один аргумент использует сопоставление, все должны использовать сопоставители. Сопоставители аргументов будут рассмотрены в следующем уроке, но, вероятно, их следует использовать с осторожностью.
  • Mockito предоставляет метод verifyNoMoreInteractions() для проверки того, что конкретный макет не имеет больше взаимодействий, но рекомендует использовать его очень экономно и только при необходимости.
  • Mockito предоставляет интерфейс Answer позволяющий создавать заглушки с обратными вызовами, однако он рекомендует не использовать его и рекомендует выполнять простые заглушки с использованием thenReturn() и doThrow() . Мы рассмотрим ответы в следующем уроке.
  • Если вы используете ArgumentCaptor для проверки аргументов, вы должны использовать его только на этапе проверки, а не на этапе создания заглушки. ArgumentCaptor будет рассмотрен в следующем уроке.
  • Mockito рекомендует использовать Partial Mocks очень осторожно, в основном при работе с устаревшим кодом. Хорошо продуманный код не должен требовать использования частичных макетов.
  • Mockito предоставляет метод reset() для сброса вашего макета в середине метода тестирования, однако он рекомендует не использовать его, так как это пахнет кодом, что ваш тест может быть слишком длинным и сложным.

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