Как вы тестируете методы, которые запускают асинхронные процессы с JUnit?
Я не знаю, как заставить мой тест ждать завершения процесса (это не совсем модульный тест, это больше похоже на интеграционный тест, поскольку он включает в себя несколько классов, а не только один).
Ответы:
ИМХО, это плохая практика - создавать модульные тесты или ждать их в потоках и т. Д. Вы хотели бы, чтобы эти тесты выполнялись за доли секунды. Вот почему я хотел бы предложить двухэтапный подход к тестированию асинхронных процессов.
источник
Альтернативой является использование класса CountDownLatch .
ПРИМЕЧАНИЕ: вы не можете просто использовать синхронизированный с обычным объектом в качестве блокировки, так как быстрые обратные вызовы могут снять блокировку до вызова метода ожидания блокировки. Смотрите это сообщение в блоге Джо Уолнесом.
РЕДАКТИРОВАТЬ Удалены синхронизированные блоки вокруг CountDownLatch благодаря комментариям @jtahlborn и @Ring
источник
Вы можете попробовать использовать библиотеку Awaitility . Это облегчает тестирование систем, о которых вы говорите.
источник
CountDownLatch
(см. Ответ @Martin) лучше в этом отношении.Если вы используете CompletableFuture (представленный в Java 8) или SettableFuture (из Google Guava ), вы можете завершить свой тест, как только он будет сделан, вместо того, чтобы ждать предварительно установленное количество времени. Ваш тест будет выглядеть примерно так:
источник
Запустите процесс и дождитесь результата, используя
Future
.источник
Один метод, который я нашел довольно полезным для тестирования асинхронных методов, - это внедрение
Executor
экземпляра в конструктор объекта для теста. В производственном процессе экземпляр executor настроен на асинхронную работу, в то время как в тесте его можно смоделировать для синхронного запуска.Итак, предположим, что я пытаюсь проверить асинхронный метод
Foo#doAsync(Callback c)
,В производстве, я бы построить
Foo
сExecutors.newSingleThreadExecutor()
экземпляром Исполнителя в то время как в тесте , я бы , наверное , построить его с синхронным исполнителем , который делает следующее -Теперь мой тест JUnit асинхронного метода довольно чистый -
источник
WebClient
В тестировании многопоточного / асинхронного кода нет ничего плохого, особенно если многопоточность является точкой кода, который вы тестируете. Общий подход к тестированию этого материала заключается в следующем:
Но это очень много для одного теста. Лучший / более простой подход - просто использовать ConcurrentUnit :
Преимущество этого
CountdownLatch
подхода в том, что он менее многословен, поскольку ошибки утверждений, возникающие в любом потоке, должным образом передаются в основной поток, что означает, что тест завершается неудачей, когда это необходимо. Запись, которая сравниваетCountdownLatch
подход к ConcurrentUnit находится здесь .Я также написал пост в блоге на эту тему для тех, кто хочет узнать немного подробнее.
источник
Как насчет вызова
SomeObject.wait
иnotifyAll
как описано здесь ИЛИ используя метод РоботиумаSolo.waitForCondition(...)
ИЛИ используйте класс, который я написал, чтобы сделать это (см. Комментарии и тестовый класс для использования)источник
Я нахожу библиотеку socket.io для проверки асинхронной логики. Это выглядит просто и кратко с помощью LinkedBlockingQueue . Вот пример :
Используя LinkedBlockingQueue, возьмите API для блокировки, пока не получите результат, как в синхронном режиме. И установите таймаут, чтобы не тратить слишком много времени на ожидание результата.
источник
Стоит отметить, что
Testing Concurrent Programs
в « Параллельности на практике» есть очень полезная глава, которая описывает некоторые подходы модульного тестирования и дает решения для проблем.источник
Это то, что я использую в настоящее время, если результат теста генерируется асинхронно.
При использовании статического импорта тест выглядит довольно хорошо. (обратите внимание, в этом примере я начинаю тему, чтобы проиллюстрировать идею)
Если
f.complete
не вызывается, тест не пройден по истечении времени ожидания. Вы также можете использовать,f.completeExceptionally
чтобы потерпеть неудачу рано.источник
Здесь есть много ответов, но простой - просто создать законченное CompletableFuture и использовать его:
Итак, в моем тесте:
Я просто следю за тем, чтобы все это называлось так или иначе. Этот метод работает, если вы используете этот код:
Это прыгнет прямо через это, поскольку все CompletableFutures закончены!
источник
Избегайте тестирования с параллельными потоками всякий раз, когда вы можете (что происходит в большинстве случаев). Это только сделает ваши тесты нестабильными (иногда проходит, иногда не проходит).
Только когда вам нужно вызвать какую-то другую библиотеку / систему, вам, возможно, придется ждать в других потоках, в этом случае всегда используйте библиотеку Awaitility вместо
Thread.sleep()
.Никогда не звоните
get()
или не проводитеjoin()
тесты, иначе ваши тесты могут работать вечно на вашем CI-сервере, если будущее никогда не закончится. Всегда утверждайтеisDone()
сначала в своих тестах перед вызовомget()
. Для CompletionStage это.toCompletableFuture().isDone()
.Когда вы тестируете неблокирующий метод, подобный этому:
тогда вам следует не просто проверить результат, пройдя завершенное Future в тесте, вы также должны убедиться, что ваш метод
doSomething()
не блокируется с помощью вызоваjoin()
илиget()
. Это особенно важно, если вы используете неблокирующую платформу.Для этого выполните тестирование с незавершенным будущим, которое вы установили как завершенное вручную:
Таким образом, если вы добавите
future.join()
doSomething (), тест не пройден.Если ваша служба использует ExecutorService, например, in
thenApplyAsync(..., executorService)
, то в ваших тестах внедряется однопотоковая служба ExecutorService, например, из guava:Если в вашем коде используется forkJoinPool, например, перепишите
thenApplyAsync(...)
код для использования ExecutorService (есть много веских причин) или используйте Awaitility.Чтобы сократить пример, я сделал BarService аргументом метода, реализованным как лямбда-код Java8 в тесте, обычно это была бы вставленная ссылка, которую вы бы высмеяли.
источник
Я предпочитаю использовать ожидание и уведомление. Это просто и понятно.
По сути, нам нужно создать окончательную ссылку на массив, которая будет использоваться внутри анонимного внутреннего класса. Я предпочел бы создать логическое значение [], потому что я могу поставить значение для управления, если нам нужно ждать (). Когда все сделано, мы просто выпускаем asyncExecuted.
источник
Для всех пользователей Spring в настоящее время я обычно делаю свои интеграционные тесты, где задействовано асинхронное поведение:
Вызвать событие приложения в производственном коде, когда асинхронная задача (например, вызов ввода-вывода) завершена. В большинстве случаев это событие необходимо в любом случае для обработки ответа асинхронной операции в рабочей среде.
После этого события вы можете использовать следующую стратегию в своем тестовом примере:
Чтобы это сломать, сначала нужно запустить некое доменное событие. Я использую UUID здесь, чтобы определить задачу, которая завершена, но вы, конечно, можете использовать что-то еще, если оно уникально.
(Обратите внимание, что следующие фрагменты кода также используют аннотации Lombok, чтобы избавиться от кода котельной пластины)
Сам производственный код обычно выглядит так:
Затем я могу использовать Spring,
@EventListener
чтобы поймать опубликованное событие в тестовом коде. Слушатель событий немного более вовлечен, потому что он должен обрабатывать два случая потокобезопасным способом:А
CountDownLatch
используется для второго случая, как указано в других ответах здесь. Также обратите внимание, что@Order
аннотация к методу обработчика событий гарантирует, что этот метод обработчика событий вызывается после любых других прослушивателей событий, используемых в производстве.Последний шаг - выполнить тестируемую систему в тестовом примере. Я использую тест SpringBoot с JUnit 5 здесь, но это должно работать одинаково для всех тестов, использующих контекст Spring.
Обратите внимание, что в отличие от других ответов здесь, это решение также будет работать, если вы выполняете свои тесты параллельно и несколько потоков одновременно выполняют асинхронный код.
источник
Если вы хотите проверить логику, просто не проверяйте ее асинхронно.
Например, чтобы протестировать этот код, который работает на результатах асинхронного метода.
В тесте макет зависимости с синхронной реализацией. Модульное тестирование полностью синхронно и выполняется за 150 мс.
Вы не проверяете асинхронное поведение, но можете проверить правильность логики.
источник