Модульное тестирование метода с использованием CompletableFuture.allOf()

Вопрос или проблема

Я пытаюсь протестировать метод, который использует 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 в списке. Метод будет незамедлительно завершаться при достижении первого успешного результата, при этом исключения будут корректно обрабатываться. Таким образом, ваш метод и тест будут соответствовать требованиям, обеспечивая заданный функционал с учетом асинхронного выполнения.

Оцените материал
Добавить комментарий

Капча загружается...