¿Cómo se prueban los métodos que activan procesos asincrónicos con JUnit?
No sé cómo hacer que mi prueba espere a que finalice el proceso (no es exactamente una prueba unitaria, es más como una prueba de integración, ya que involucra varias clases y no solo una).
Respuestas:
En mi humilde opinión, es una mala práctica hacer que las pruebas unitarias creen o esperen subprocesos, etc. Le gustaría que estas pruebas se ejecuten en segundos. Es por eso que me gustaría proponer un enfoque de 2 pasos para probar procesos asíncronos.
fuente
Una alternativa es usar la clase CountDownLatch .
NOTA: no puede simplemente usar sincronizado con un objeto normal como bloqueo, ya que las devoluciones de llamada rápidas pueden liberar el bloqueo antes de que se llame al método de espera del bloqueo. Vea esta publicación de blog de Joe Walnes.
EDITAR Se eliminaron los bloques sincronizados alrededor de CountDownLatch gracias a los comentarios de @jtahlborn y @Ring
fuente
Puedes intentar usar el biblioteca Awaitility . Facilita la prueba de los sistemas de los que habla.
fuente
CountDownLatch
(ver respuesta de @ Martin) es mejor en este sentido.Si usa un CompletableFuture (introducido en Java 8) o un SettableFuture (de Google Guava ), puede hacer que su prueba finalice tan pronto como termine, en lugar de esperar una cantidad de tiempo preestablecida. Su prueba se vería así:
fuente
Comience el proceso y espere el resultado usando a
Future
.fuente
Un método que he encontrado bastante útil para probar métodos asincrónicos es inyectar un
Executor
instancia en el constructor del objeto a prueba. En producción, la instancia del ejecutor está configurada para ejecutarse de forma asíncrona, mientras que en la prueba se puede burlar para ejecutarse de forma sincrónica.Supongamos que estoy tratando de probar el método asincrónico
Foo#doAsync(Callback c)
,En producción, construiría
Foo
con unaExecutors.newSingleThreadExecutor()
instancia de Ejecutor, mientras que en la prueba probablemente lo construiría con un ejecutor síncrono que hace lo siguiente:Ahora mi prueba JUnit del método asincrónico está bastante limpia:
fuente
WebClient
No hay nada intrínsecamente malo en probar el código roscado / asíncrono, particularmente si el tema del código que está probando es enhebrar . El enfoque general para probar estas cosas es:
Pero eso es mucho repetitivo para una prueba. Un enfoque mejor / más simple es usar ConcurrentUnit :
El beneficio de esto sobre el
CountdownLatch
enfoque es que es menos detallado ya que las fallas de aserción que ocurren en cualquier hilo se informan correctamente al hilo principal, lo que significa que la prueba falla cuando debería. Una valoración crítica que compara elCountdownLatch
enfoque de ConcurrentUnit es aquí .También escribí una publicación de blog sobre el tema para aquellos que quieran aprender un poco más de detalle.
fuente
¿Qué tal llamar
SomeObject.wait
ynotifyAll
como se describe aquí O usar el método RobotiumsSolo.waitForCondition(...)
O usar una clase que escribí para hacer esto? (Ver comentarios y clase de prueba para saber cómo usar)fuente
Encuentro una biblioteca socket.io para probar la lógica asincrónica. Se ve de manera simple y breve usando LinkedBlockingQueue . Aquí hay un ejemplo :
Usando LinkedBlockingQueue, tome la API para bloquear hasta obtener el resultado de forma síncrona. Y establezca el tiempo de espera para evitar asumir demasiado tiempo para esperar el resultado.
fuente
Vale la pena mencionar que hay un capítulo muy útil
Testing Concurrent Programs
en Concurrencia en la práctica que describe algunos enfoques de pruebas unitarias y brinda soluciones para los problemas.fuente
Esto es lo que estoy usando hoy en día si el resultado de la prueba se produce de forma asincrónica.
Usando importaciones estáticas, la prueba dice algo agradable. (nota, en este ejemplo estoy comenzando un hilo para ilustrar la idea)
Si
f.complete
no se llama, la prueba fallará después de un tiempo de espera. También puedes usarf.completeExceptionally
para fallar temprano.fuente
Aquí hay muchas respuestas, pero una simple es crear un CompletableFuture completo y usarlo:
Entonces en mi prueba:
Solo me estoy asegurando de que todas estas cosas se llamen de todos modos. Esta técnica funciona si está utilizando este código:
¡Se deslizará a través de él a medida que terminen todos los CompletableFutures!
fuente
Evite realizar pruebas con subprocesos paralelos siempre que pueda (que es la mayoría de las veces). Esto solo hará que sus pruebas sean escamosas (a veces pasa, a veces falla).
Solo cuando necesite llamar a otra biblioteca / sistema, es posible que tenga que esperar en otros hilos, en ese caso siempre use la biblioteca Awaitility en lugar de
Thread.sleep()
.Nunca solo llame
get()
ojoin()
en sus pruebas, de lo contrario, sus pruebas podrían ejecutarse para siempre en su servidor CI en caso de que el futuro nunca se complete. Siempre afirmeisDone()
primero en sus pruebas antes de llamarget()
. Para CompletionStage, eso es.toCompletableFuture().isDone()
.Cuando prueba un método sin bloqueo como este:
entonces no solo debe probar el resultado pasando un Futuro completo en la prueba, también debe asegurarse de que su método
doSomething()
no se bloquee llamandojoin()
oget()
. Esto es importante en particular si utiliza un marco sin bloqueo.Para hacer eso, pruebe con un futuro no completado que configuró para completar manualmente:
De esa manera, si agrega
future.join()
a doSomething (), la prueba fallará.Si su Servicio utiliza un Servicio de Ejecutor como en
thenApplyAsync(..., executorService)
, entonces en sus pruebas inyecte un Servicio de Ejecutor de un solo subproceso, como el de guayaba:Si su código usa forkJoinPool como
thenApplyAsync(...)
, por ejemplo , reescriba el código para usar un ExecutorService (hay muchas buenas razones) o use Awaitility.Para acortar el ejemplo, hice de BarService un argumento de método implementado como un lambda Java8 en la prueba, por lo general, sería una referencia inyectada de la que te burlarías.
fuente
Prefiero usar esperar y notificar. Es simple y claro.
Básicamente, necesitamos crear una referencia de matriz final, para ser utilizada dentro de la clase interna anónima. Prefiero crear un booleano [], porque puedo poner un valor para controlar si necesitamos esperar (). Cuando todo está hecho, solo lanzamos asyncExecuted.
fuente
Para todos los usuarios de Spring, así es como suelo hacer mis pruebas de integración hoy en día, donde está involucrado el comportamiento asíncrono:
Active un evento de aplicación en el código de producción, cuando una tarea asíncrona (como una llamada de E / S) haya finalizado. La mayoría de las veces este evento es necesario de todos modos para manejar la respuesta de la operación asíncrona en producción.
Con este evento en su lugar, puede utilizar la siguiente estrategia en su caso de prueba:
Para desglosar esto, primero necesitará algún tipo de evento de dominio para disparar. Estoy usando un UUID aquí para identificar la tarea que se ha completado, pero por supuesto eres libre de usar otra cosa siempre que sea única.
(Tenga en cuenta que los siguientes fragmentos de código también usan anotaciones de Lombok para deshacerse del código de la placa de la caldera)
El código de producción en sí normalmente se ve así:
Entonces puedo usar un Spring
@EventListener
para capturar el evento publicado en el código de prueba. El oyente de eventos está un poco más involucrado, porque tiene que manejar dos casos de manera segura para subprocesos:A
CountDownLatch
se usa para el segundo caso como se menciona en otras respuestas aquí. También tenga en cuenta que la@Order
anotación en el método de controlador de eventos asegura que este método de controlador de eventos se llama después de cualquier otro detector de eventos utilizado en producción.El último paso es ejecutar el sistema bajo prueba en un caso de prueba. Estoy usando una prueba SpringBoot con JUnit 5 aquí, pero esto debería funcionar igual para todas las pruebas que usan un contexto Spring.
Tenga en cuenta que, a diferencia de otras respuestas aquí, esta solución también funcionará si ejecuta sus pruebas en paralelo y múltiples hilos ejercen el código asíncrono al mismo tiempo.
fuente
Si desea probar la lógica, no la pruebe de forma asincrónica.
Por ejemplo, para probar este código que funciona en los resultados de un método asincrónico.
En la prueba simula la dependencia con implementación síncrona. La prueba de la unidad es completamente sincrónica y se ejecuta en 150 ms.
No prueba el comportamiento asíncrono pero puede probar si la lógica es correcta.
fuente