Вопрос или проблема
Я пытаюсь протестировать метод, который использует CompletableFuture.allOf()
. Это мой метод:
static CompletableFuture<byte[]> anySuccess(List<CompletableFuture<byte[]>> futures) {
CompletableFuture<byte[]> delegateFuture = new CompletableFuture<>();
CompletableFuture.allOf(futures.stream().map(s -> s.thenApply(t -> {
if (t == null) {
// Здесь мы рассматриваем null как неудачный поиск.
// Внутреннее фиктивное исключение служит сигналом для вызывающего кода вернуть null.
throw new RuntimeException();
}
return t;
}).thenAccept(delegateFuture::complete)).toArray(CompletableFuture<?>[]::new))
.exceptionally(ex -> {
delegateFuture.completeExceptionally(ex);
return null;
});
return delegateFuture;
}
Входной набор CompletableFuture может завершаться/ожидается, что они завершаются в разное время. Вот как выглядит мой юнит-тест:
@Test
void testAnySuccess() {
final byte[] payload1 = "payload1".getBytes();
final byte[] payload2 = "payload2".getBytes();
List<CompletableFuture<byte[]>> futures = Arrays.asList(
CompletableFuture.supplyAsync(
() -> null
),
CompletableFuture.supplyAsync(
() -> {
LockSupport.parkNanos(Duration.ofSeconds(5).toNanos());
System.out.println("Жду 5 секунд");
return payload2;
}),
CompletableFuture.supplyAsync(
() -> {
LockSupport.parkNanos(Duration.ofMillis(100).toNanos());
System.out.println("Жду 100мс");
return payload1;
})
);
long start = System.nanoTime();
CompletableFuture<byte[]> res = anySuccess(futures);
byte[] actualRes = res.join();
Assert.assertEquals(actualRes, payload1);
Насколько я понимаю, метод allOf()
не блокирует, но он возвращает другой CompletableFuture, который завершается только тогда, когда все будущее в нем завершены.
Вот мой ожидаемый результат – вернуть первое ненулевое значение, или если все они нулевые, то вернуть исключение.
Я не уверен, нужно ли мне использовать метод allOf
или anyOf
в этом конкретном случае. Или мне нужно изменить код так, чтобы я не использовал метод .exceptionally(...)
, и попробовать изменить его с помощью thenAccept(...)
Ответ или решение
Чтобы успешно протестировать метод, использующий CompletableFuture.allOf()
, важно понять логику работы асинхронного программирования в Java и особенности CompletableFuture
. Давайте разберем ваш метод и возможные подходы к юнит-тестированию с учетом различных сценариев.
1. Анализ метода anySuccess
Метод anySuccess
принимает список CompletableFuture<byte[]>
и возвращает CompletableFuture<byte[]>
, который завершится, как только один из входящих CompletableFuture
завершится успешно (с ненулевым значением). Если все CompletableFuture
завершились с null
, метод должен генерировать исключение.
Использование allOf
против anyOf
CompletableFuture.allOf(...)
будет ждать завершения всех переданныхCompletableFuture
, что не совсем соответствует вашему требованию о возвращении первого успешного результата. В вашем случае, использованиеanyOf
будет предпочтительнее, поскольку этот метод завершится, как только один изCompletableFuture
будет завершен успешно.
2. Проблемы в текущей реализации
В текущей реализации возникла ошибка логики. Вы используете allOf
, чтобы обрабатывать CompletableFuture
, в то время как ваша задача требует, чтобы вы завершили CompletableFuture
, когда достигнется первый успешный результат. Это следует исправить, заменив allOf
на anyOf
.
3. Переписанный метод
static CompletableFuture<byte[]> anySuccess(List<CompletableFuture<byte[]>> futures) {
CompletableFuture<byte[]> delegateFuture = new CompletableFuture<>();
CompletableFuture.anyOf(futures.stream().map(future ->
future.thenApply(result -> {
if (result == null) {
throw new RuntimeException("Null result"); // Указываем, что это ошибка
}
return result;
}).exceptionally(ex -> {
return null; // Игнорируем, если возникло исключение
})).toArray(CompletableFuture[]::new))
.thenAccept(delegateFuture::complete)
.exceptionally(ex -> {
delegateFuture.completeExceptionally(ex);
return null;
});
return delegateFuture;
}
4. Юнит-тест с использованием anyOf
Теперь вам нужно обновить существующий тест, чтобы он соответствовал новой логике. Пример теста:
@Test
void testAnySuccess() {
final byte[] payload1 = "payload1".getBytes();
final byte[] payload2 = "payload2".getBytes();
List<CompletableFuture<byte[]>> futures = Arrays.asList(
CompletableFuture.supplyAsync(() -> null),
CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(Duration.ofSeconds(5).toNanos());
return payload2;
}),
CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(Duration.ofMillis(100).toNanos());
return payload1;
})
);
CompletableFuture<byte[]> res = anySuccess(futures);
byte[] actualRes = res.join();
Assert.assertArrayEquals(payload1, actualRes);
}
5. Заключение
Использование CompletableFuture.anyOf(...)
позволяет вам достичь желаемого поведения без ожидания завершения всех CompletableFuture
в списке. Метод будет незамедлительно завершаться при достижении первого успешного результата, при этом исключения будут корректно обрабатываться. Таким образом, ваш метод и тест будут соответствовать требованиям, обеспечивая заданный функционал с учетом асинхронного выполнения.