Статьи

Обходной путь к многопоточному тестированию

Так как он был представлен в JDK 1.5, мне понравилась абстракция Executor по многопоточному исполнению. В основном вы определяете задачи, реализуя интерфейсы Runnable или Callable , и отправляете эти задачи в реализацию Executor . Это Исполнитель, который знает, как задачи должны быть обработаны: запланированы на определенное время, поставлены в очередь в отдельном потоке или с использованием пула потоков. Различные экземпляры Executor могут быть получены через класс Executors .
Таким образом, логика вашей программы не зависит от того, как должна быть реализована многопоточность: вы можете думать о задачах и исполнителях, и позже вы можете выбрать, как эти задачи должны быть обработаны. Также возможно узнать статус задачи: взглянув на будущее .

Еще одно преимущество, которое мне нравится, это то, что вы можете устранить проблему многопоточности во время тестов. Тестирование многопоточного кода довольно сложно, потому что в то время, когда вы хотите проверить свои утверждения, параллельные потоки могут быть еще не готовы, поэтому вам придется поиграть с методами sleep () , join () , wait () и notify () , производящий иногда ненадежные тесты.

Так что, не было бы замечательно, если бы, просто для тестирования, вы могли убрать сложность фонового выполнения? В конце концов, мы знаем, что исполнители работают прекрасно, и нам не нужно их проверять.
На самом деле, как говорят Javadocs

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

class DirectExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}

Следуя принципу DIP , вы можете передать вышеупомянутый DirectExecutor классу, который вы хотите протестировать — я обычно делаю это в конструкторе — и во время тестов вы обошли вокруг факта, что что-то в реальном мире будет происходить в фоновом режиме. Другими словами, вы можете сгладить несколько потоков в одном потоке.

Пример:

String message = "hello world!";
Executor executor = new DirectExecutor();
Chat chat = new Chat(executor);
// suppose that the sendMessage sends messages in background (async)
chat.sendMessage(message);
assertEqual(message, chatServer.lastMessageReceived());

Как только мы использовали DirectExecutor, мы знаем, что когда мы вызываем sendMessage (), выполнение логики, стоящей за ним, теперь будет синхронным. Затем утверждение в следующей строке может оценить результат, не дожидаясь завершения «фонового» процесса. Больше не нужно спать и координировать нить.

DirectExecutor, как указано в Javadocs, может быть улучшен, чтобы быть более эффективным для целей тестирования. Например, в Mockito вы можете реализовать прямого исполнителя с Mock Object, который также может быть запрошен, чтобы проверить, как тестируемый класс взаимодействует с ним.

// on the base test class (MockitoTestBase)
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}

protected void implementAsDirectExecutor(ExecutorService executor) {
doAnswer(new Answer<Object>() {
public Object answer(InvocationOnMock invocation)
throws Exception {
Object[] args = invocation.getArguments();
Runnable runnable = (Runnable)args[0];
runnable.run();
return null;
}
}).when(executor).submit(any(Runnable.class));
}

// on the subclass

@Mock private ExecutorService executor;

@Before
public void setUp() {
implementAsDirectExecutor(executor);
}

public void testChat() {
String message = "hello world!";
Chat chat = new Chat(executor);
// suppose that sendMessage sends messages in background (async)
chat.sendMessage(message);
// verify that the submit method has been invoked
Mockito.verify(executor, times(1)).sumit(...);
assertEqual(message, chatServer.lastMessageReceived());
}

В приведенном выше примере я использовал ExecutorService , который является подчиненным интерфейсом Executor и предоставляет дополнительные функции (например, выключение исполнителя и возможность отклонять задачи). Но по сути это более или менее то же самое.

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

С http://en.newinstance.it/2010/09/07/workaround-to-multi-threaded-testing/