Для тех из моих (на момент написания этого сообщения потенциальных) читателей, которые не знакомы с этим замечательным инструментом для тестирования компонентов, я решил написать сей пост с небольшим вводным курсом в технологию и примерами использования.
Жизненный цикл
Для начала давайте ознакомимся с тем какие стадии проходит "замоченный" компонент.
- Создание mock компонента - на этой стадии для заданного интерфейса или класса создается прокси объект, который будет перехватывать все вызовы.
- Запись сценария - после создания, mock переходит в record state. Это значит, что все вызовы, обращенные к нему, будут запомнены. Кроме того на этом шаге можно (и нужно) задать ожидаемое поведение для вызванного метода.
- Воспроизведение сценария - с помощью helper метода replay, mocked объект переводится в состояние воспроизведения. Это значит, что все вызовы обращенные к нему будут сравнены с теми, которые были записаны, а так же будет воспроизведено записанное поведение (такое как возвращаемое значение).
- Верификация - с помощью helper метода verify производится проверка того насколько полно был выполнен сценарий. Если какие-то из записанных методов не были вызваны, то это приведет к вызову исключительной ситуации.
Тестовый стенд
Рассмотрим использование mock компонентов, на примере сервиса отправки почты. Допустим, у нас есть ContactService, который содержит ссылку на MailService и использует его для отправки почты.
MailService.java
public interface MailService {
   public boolean send(String subject, String body, String to);
}
MailServiceImpl.java
public class MailServiceImpl { 
  //...
  public boolean send(String subject, String body, String to)  {
    //...
  }
  //...
}
ContactService.java
public class ContactService {
  private MailService mailService;
  private String email;
  public void setEmail(String email) {
    this.email = email;
  }
  public String getEmail() {
    return email;
  }
  public void setMailService(MailService mailService) {
    this.mailService = mailService;
  }
  public void sendEmail(String subject, String body) {
    mailService.send(subject, body, email);
  }
}
Создание mock компонента
Самый простой способ - это использовать статический метод EasyMock#createMock.
MailService mailService = EasyMock.createMock(MailService.class); // теперь mailService proxy объект ContactServiceImpl contactService = new ContactServiceImpl(); // создаем как обычно contactService.setMailService(mailService); // выставляем proxy объект, в качестве e-mail сервиса
Таким образом, мы создали mocked объект и связали его с тестируемым объектом. Но, пока что, использовать мы его не можем. Вызов любого метода будет возвращать null в качестве результата.
Возможности EasyMock этим не ограничиваются. Например, с его помощью можно создавать partial-mocks (только часть методов перехватывается, а остальные работают как обычно), задавать параметры конструктора и какой конструктор использовать, задавать правила выполнения сценария (в той же последовательности, что и записан; в произвольной последовательности и т.п.)
Запись сценария:
Для того, чтобы как-то оживить наш прокси объект, мы должны записать для него сценарий, по которому он будет работать:
mailService.send("Hello", "Hello, world", "user@mail.com"); // записываем вызов метода с перечисленными параметрами
expectLastCall().andReturn(true); // записываем, что мы ожидаем вызов предыдущего метода и в качестве результата хотим вернуть true
Этот же сценарий в более короткой форме можно записать так:
expect(mailService.send("Hello", "Hello, world", "user@mail.com")).andReturn(true);
Опять же, это лишь вершина айсберга. EasyMock позволяет использовать условия для параметров, помимо четкого соответствия (регулярное выражение, больше/меньше и т.д.); "захватывать" значения переданных параметров для дальнейшей проверки или обработки; вызывать произвольный блок кода, в ответ на вызов функции по сценарию (например, для вычисления возвращаемого значения на основе ранее "захваченного" параметра); задавать другие параметры поведения, такие как, количество вызовов метода; создавать stub методы (методы, которые всегда возвращают одно и то же значение, независимо от значений параметров и количества вызовов).
Воспроизведение сценария:
Теперь мы можем перейти в режим воспроизведения и вызвать метод contactService, который, в свою очередь, обратится к mocked MailService объекту
EasyMock.replay(mailService); // сообщаем, что прокси объект mailService, нужно перевести в режим воспроизведения
contactService.setEmail("user@mail.com");
contactService.sendEmail("Hello", "Hello, world"); // внутри метода будет вызван mailService.send("Hello", "Hello, world", "user@mail.com"), который вернет true, как и было записано в нашем сценарии
Если какие-либо параметры не совпадут, то вызов mailService#send приведет к ошибке. То же самое произойдет, если метод будет вызван повторно, если это не было разрешено в сценарии, либо вообще не присутствует в сценарии, либо был вызван не в том порядке. Поведение может меняться, в зависимости от механизма, который был использован для создания mock объекта.
Верификация
Этот шаг используется для того, чтобы убедиться, что все методы сценария были выполнены и осуществляется простым вызовом статического метода EasyMock#verify
EasyMock.verify(mailService); // проверить правильность воспроизведения сценария
Итог:
В итоге выполнения всех этих шагов мы получим следующий тест:
public void ContactServiceTest {
  @Test
  public void testThatItSendsEmail() throws Exception {
    MailService mailService = EasyMock.createMock(MailService.class);
    ContactServiceImpl contactService = new ContactServiceImpl();
    contactService.setMailService(mailService);
    expect(mailService.send("Hello", "Hello, world", "user@mail.com")).andReturn(true);
    EasyMock.replay(mailService);
    contactService.setEmail("user@mail.com");
    contactService.sendEmail("Hello", "Hello, world");
    EasyMock.verify(mailService);
  }
}
Примерно так выглядит простой тест, написанный с использованием EasyMock. Более подробную информацию об этой библиотеке можно получить на официальном сайте: http://easymock.org/.
 
Вы очень внятно и понятно все объяснили, благодарю!
ОтветитьУдалить