Статьи

Моделирование трудоемких действий в интеграционных тестах

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

Задание.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package pl.grzejszczak.marcin;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
/**
 * Service that does some things including processing of the external service
 *
 * @author marcin
 *
 */
public class SomeTask implements Runnable {
 
 private static final Logger LOGGER = LoggerFactory.getLogger(SomeTask.class);
 
 // Service is injected via a dependency injection system
 private Processable timeConsumingExternalService;
 
 private void methodThatConnectsToExternalServices() {
  // connects to an external service and spends a couple of seconds there
  LOGGER.debug("Before processing");
  timeConsumingExternalService.process();
  LOGGER.debug("After processing");
  // some other things to do
 }
 
 public void run() {
  methodThatConnectsToExternalServices();
 }
 
 public void setTimeConsumingExternalService(Processable timeConsumingExternalService) {
  this.timeConsumingExternalService = timeConsumingExternalService;
 }
 
}

Интеграционный тест.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package pl.grzejszczak.marcin;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class ServiceIntegrationTest {
 
 private static final Logger LOGGER = LoggerFactory.getLogger(ServiceIntegrationTest.class);
 
 private ExecutorService executorService = Executors.newCachedThreadPool();
 private Processable timeConsumingExternalServiceMock = Mockito.mock(Processable.class);
 private SomeTask someTask = new SomeTask();
 
 public ServiceIntegrationTest() {
  initializeMocks();
 }
 
 private void initializeMocks() {
  Mockito.doAnswer(new Answer<Object>() {
   public Object answer(InvocationOnMock invocation) throws Throwable {
    // Simulation of connection to external services
    LOGGER.debug("Sleeping");
    Thread.sleep(5000);
    LOGGER.debug("Stopped Sleeping");
    return null;
   }
  }).when(timeConsumingExternalServiceMock).process();
  // Inject the mock to the Task - in any possible way
  someTask.setTimeConsumingExternalService(timeConsumingExternalServiceMock);
 }
 
 public void executeTest() {
  executorService.execute(someTask);
 }
 
 public static void main(String args[]) {
  ServiceIntegrationTest integrationTest = new ServiceIntegrationTest();
  integrationTest.executeTest();
 }
}

И вывод на консоль:

1
2
3
4
5
6
7
2012-10-07 22:42:37,378 DEBUG pl.grzejszczak.marcin.SomeTask:21 Before processing
 
2012-10-07 22:42:37,389 DEBUG pl.grzejszczak.marcin.ServiceIntegrationTest:28 Sleeping
 
2012-10-07 22:42:42,390 DEBUG pl.grzejszczak.marcin.ServiceIntegrationTest:30 Stopped Sleeping
 
2012-10-07 22:42:42,392 DEBUG pl.grzejszczak.marcin.SomeTask:23 After processing

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

1
2
3
4
5
6
7
8
9
Mockito.doAnswer(new Answer<Object>() {
   public Object answer(InvocationOnMock invocation) throws Throwable {
    // Simulation of connection to external services
    LOGGER.debug("Sleeping");
    Thread.sleep(5000);
    LOGGER.debug("Stopped Sleeping");
    return null;
   }
  }).when(timeConsumingExternalServiceMock).process();

Этот фрагмент кода изменяет действие по умолчанию, которое должно быть выполнено данным объектом при выполнении данного метода. В данном конкретном случае нам пришлось смоделировать метод, который возвращает void — поэтому мы начинаем с doAnswer (…) и заканчиваем когда when (…) .process (). Таким образом, в интеграционном тесте мне удалось создать симуляцию ожидания завершения службы. Если у вас есть какие-либо идеи или комментарии о том, как бы вы сделали это по-другому, пожалуйста, не стесняйтесь оставить комментарий ниже