Como você testa métodos que acionam processos assíncronos com o JUnit?
Não sei como fazer meu teste aguardar o término do processo (não é exatamente um teste de unidade, é mais como um teste de integração, pois envolve várias classes e não apenas uma).
Respostas:
IMHO, é uma prática ruim ter testes de unidade para criar ou aguardar threads, etc. Você gostaria que esses testes fossem executados em segundos. É por isso que eu gostaria de propor uma abordagem em duas etapas para testar processos assíncronos.
fonte
Uma alternativa é usar a classe CountDownLatch .
NOTA: você não pode apenas usar sincronizado com um objeto comum como um bloqueio, pois retornos de chamada rápidos podem liberar o bloqueio antes que o método de espera do bloqueio seja chamado. Veja este post de Joe Walnes.
EDIT Removidos blocos sincronizados em torno do CountDownLatch, graças aos comentários de @jtahlborn e @Ring
fonte
Você pode tentar usar a biblioteca Awaitility . Isso facilita o teste dos sistemas dos quais você está falando.
fonte
CountDownLatch
(ver resposta de @Martin) é melhor nesse sentido.Se você usar um CompletableFuture (introduzido no Java 8) ou um SettableFuture (do Google Guava ), poderá fazer o teste terminar assim que terminar, em vez de esperar um período de tempo predefinido. Seu teste seria mais ou menos assim:
fonte
Inicie o processo e aguarde o resultado usando a
Future
.fonte
Um método que achei bastante útil para testar métodos assíncronos é injetar uma
Executor
instância no construtor do objeto a testar. Na produção, a instância do executor está configurada para ser executada de forma assíncrona, enquanto no teste ela pode ser zombada para ser executada de forma síncrona.Então, suponha que eu esteja tentando testar o método assíncrono
Foo#doAsync(Callback c)
,Na produção, eu construiria
Foo
com umaExecutors.newSingleThreadExecutor()
instância Executor, enquanto no teste provavelmente a construiria com um executor síncrono que faz o seguinte -Agora, meu teste JUnit do método assíncrono está bastante limpo -
fonte
WebClient
Não há nada inerentemente errado com o teste de código encadeado / assíncrono, principalmente se o threading for o ponto do código que você está testando. A abordagem geral para testar essas coisas é:
Mas isso é muita clichê para um teste. Uma abordagem melhor / mais simples é usar apenas o ConcurrentUnit :
O benefício disso sobre a
CountdownLatch
abordagem é que é menos detalhado, pois as falhas de asserção que ocorrem em qualquer encadeamento são relatadas adequadamente ao encadeamento principal, o que significa que o teste falha quando deveria. Um artigo que compara aCountdownLatch
abordagem do ConcurrentUnit está aqui .Também escrevi um post sobre o assunto para quem quer aprender um pouco mais detalhadamente.
fonte
Que tal chamar
SomeObject.wait
enotifyAll
como descrito aqui OU usando o método RobotiumsSolo.waitForCondition(...)
OU usar uma classe que eu escrevi para fazer isso (consulte os comentários e a classe de teste para saber como usar)fonte
Eu encontro uma biblioteca socket.io para testar a lógica assíncrona. Parece uma maneira simples e breve usando o LinkedBlockingQueue . Aqui está um exemplo :
Usando o LinkedBlockingQueue, a API é bloqueada até obter o resultado da mesma forma síncrona. E defina o tempo limite para evitar assumir muito tempo para aguardar o resultado.
fonte
Vale ressaltar que há um capítulo muito útil
Testing Concurrent Programs
no Concurrency in Practice, que descreve algumas abordagens de teste de unidade e fornece soluções para problemas.fonte
É isso que estou usando hoje em dia se o resultado do teste for produzido de forma assíncrona.
Usando importações estáticas, o teste é agradável. (observe, neste exemplo, estou iniciando um tópico para ilustrar a ideia)
Se
f.complete
não for chamado, o teste falhará após um tempo limite. Você também podef.completeExceptionally
falhar cedo.fonte
Há muitas respostas aqui, mas uma simples é apenas criar um CompletableFuture completo e usá-lo:
Então, no meu teste:
Estou apenas garantindo que todas essas coisas sejam chamadas assim mesmo. Essa técnica funciona se você estiver usando este código:
Ele passará através dele quando todos os CompletableFutures estiverem concluídos!
fonte
Evite testar com threads paralelos sempre que puder (o que é na maioria das vezes). Isso só tornará seus testes esquisitos (algumas vezes passam, outras vezes falham).
Somente quando você precisar chamar alguma outra biblioteca / sistema, poderá ser necessário aguardar outros threads; nesse caso, sempre use a biblioteca Awaitility em vez de
Thread.sleep()
.Nunca chame apenas
get()
oujoin()
em seus testes; caso contrário, seus testes poderão ser executados para sempre no servidor de IC, caso o futuro nunca seja concluído. Sempre afirmeisDone()
primeiro em seus testes antes de ligarget()
. Para CompletionStage, é isso.toCompletableFuture().isDone()
.Quando você testa um método sem bloqueio como este:
então você não deve apenas testar o resultado passando um futuro concluído no teste, mas também certificar-se de que seu método
doSomething()
não seja bloqueado chamandojoin()
orget()
. Isso é importante em particular se você usar uma estrutura sem bloqueio.Para fazer isso, teste com um futuro não concluído que você definiu como concluído manualmente:
Dessa forma, se você adicionar
future.join()
ao doSomething (), o teste falhará.Se o seu Serviço usar um ExecutorService como em
thenApplyAsync(..., executorService)
, em seus testes injete um ExecutorService de thread único, como o da goiaba:Se o seu código usar o forkJoinPool como
thenApplyAsync(...)
, reescreva o código para usar um ExecutorService (existem muitas boas razões) ou use Awaitility.Para encurtar o exemplo, fiz do BarService um argumento de método implementado como um lambda Java8 no teste, normalmente seria uma referência injetada que você zombaria.
fonte
Eu prefiro usar esperar e notificar. É simples e claro.
Basicamente, precisamos criar uma referência Array final, a ser usada dentro da classe interna anônima. Prefiro criar um booleano [], porque posso colocar um valor para controlar se precisarmos esperar (). Quando tudo estiver pronto, apenas lançamos o asyncExecuted.
fonte
Para todos os usuários do Spring, é assim que eu costumo fazer meus testes de integração hoje em dia, onde o comportamento assíncrono está envolvido:
Dispare um evento de aplicativo no código de produção, quando uma tarefa assíncrona (como uma chamada de E / S) for concluída. Na maioria das vezes, esse evento é necessário para lidar com a resposta da operação assíncrona na produção.
Com esse evento, você pode usar a seguinte estratégia no seu caso de teste:
Para quebrar isso, primeiro você precisará de algum tipo de evento de domínio para disparar. Estou usando um UUID aqui para identificar a tarefa que foi concluída, mas é claro que você pode usar outra coisa, desde que ela seja única.
(Observe que os seguintes trechos de código também usam anotações Lombok para se livrar do código da placa da caldeira)
O código de produção em si normalmente fica assim:
Posso usar um Spring
@EventListener
para capturar o evento publicado no código de teste. O ouvinte de evento é um pouco mais envolvido, porque precisa lidar com dois casos de uma maneira segura para threads:A
CountDownLatch
é usado para o segundo caso, como mencionado em outras respostas aqui. Observe também que a@Order
anotação no método manipulador de eventos garante que esse método manipulador de eventos seja chamado após qualquer outro ouvinte de evento usado na produção.O último passo é executar o sistema em teste em um caso de teste. Estou usando um teste SpringBoot com o JUnit 5 aqui, mas isso deve funcionar da mesma forma para todos os testes usando um contexto Spring.
Observe que, ao contrário de outras respostas aqui, esta solução também funcionará se você executar seus testes em paralelo e vários segmentos exercitarem o código assíncrono ao mesmo tempo.
fonte
Se você quiser testar a lógica, não a teste de forma assíncrona.
Por exemplo, para testar este código que funciona com resultados de um método assíncrono.
No teste, simule a dependência com a implementação síncrona. O teste de unidade é completamente síncrono e é executado em 150ms.
Você não testa o comportamento assíncrono, mas pode testar se a lógica está correta.
fonte