Comment testez-vous les méthodes qui déclenchent des processus asynchrones avec JUnit?
Je ne sais pas comment faire attendre mon test jusqu'à la fin du processus (ce n'est pas exactement un test unitaire, c'est plutôt un test d'intégration car il implique plusieurs classes et pas une seule).
Réponses:
À mon humble avis, il est mauvais d'avoir des tests unitaires créer ou attendre sur les threads, etc. Vous souhaitez que ces tests s'exécutent en quelques secondes. C'est pourquoi j'aimerais proposer une approche en 2 étapes pour tester les processus asynchrones.
la source
Une alternative consiste à utiliser la classe CountDownLatch .
REMARQUE, vous ne pouvez pas simplement utiliser la synchronisation avec un objet normal comme verrou, car les rappels rapides peuvent libérer le verrou avant l'appel de la méthode d'attente du verrou. Voir cet article de blog de Joe Walnes.
EDIT Suppression des blocs synchronisés autour de CountDownLatch grâce aux commentaires de @jtahlborn et @Ring
la source
Vous pouvez essayer d'utiliser la bibliothèque Awaitility . Cela facilite le test des systèmes dont vous parlez.
la source
CountDownLatch
(voir la réponse de @Martin) est meilleure à cet égard.Si vous utilisez un CompletableFuture (introduit dans Java 8) ou un SettableFuture (de Google Guava ), vous pouvez terminer votre test dès qu'il est terminé, plutôt que d'attendre une durée prédéfinie. Votre test ressemblerait à ceci:
la source
Démarrez le processus et attendez le résultat à l'aide de a
Future
.la source
Une méthode que j'ai trouvée assez utile pour tester des méthodes asynchrones consiste à injecter une
Executor
instance dans le constructeur de l'objet à tester. En production, l'instance de l'exécuteur est configurée pour s'exécuter de manière asynchrone tandis qu'en test, elle peut être simulée pour s'exécuter de manière synchrone.Supposons donc que j'essaie de tester la méthode asynchrone
Foo#doAsync(Callback c)
,En production, je construirais
Foo
avec uneExecutors.newSingleThreadExecutor()
instance d'Executor tandis qu'en test, je la construirais probablement avec un exécuteur synchrone qui fait ce qui suit -Maintenant, mon test JUnit de la méthode asynchrone est assez propre -
la source
WebClient
Il n'y a rien de mal à tester le code thread / asynchrone, en particulier si le thread est le point du code que vous testez. L'approche générale pour tester ce genre de choses est de:
Mais c'est beaucoup de passe-partout pour un test. Une approche meilleure / plus simple consiste à simplement utiliser ConcurrentUnit :
L'avantage de cela sur l'
CountdownLatch
approche est qu'il est moins détaillé car les échecs d'assertion qui se produisent dans n'importe quel thread sont correctement signalés au thread principal, ce qui signifie que le test échoue quand il le devrait. Un article qui compare l'CountdownLatch
approche de ConcurrentUnit est ici .J'ai également écrit un blog sur le sujet pour ceux qui veulent en savoir un peu plus.
la source
Que diriez-vous d'appeler
SomeObject.wait
etnotifyAll
comme décrit ici OU d'utiliser la méthode RobotiumsSolo.waitForCondition(...)
OU d'utiliser une classe que j'ai écrite pour le faire (voir les commentaires et la classe de test pour savoir comment utiliser)la source
Je trouve une bibliothèque socket.io pour tester la logique asynchrone. Il semble simple et bref en utilisant LinkedBlockingQueue . Voici un exemple :
En utilisant LinkedBlockingQueue, prenez l'API pour bloquer jusqu'à obtenir le résultat de la même manière que synchrone. Et définissez un délai pour éviter de supposer trop de temps pour attendre le résultat.
la source
Il convient de mentionner qu'il existe un chapitre très utile
Testing Concurrent Programs
dans Concurrence en pratique qui décrit certaines approches de test unitaire et donne des solutions aux problèmes.la source
C'est ce que j'utilise de nos jours si le résultat du test est produit de manière asynchrone.
En utilisant les importations statiques, le test se lit plutôt bien. (note, dans cet exemple, je commence un fil pour illustrer l'idée)
S'il
f.complete
n'est pas appelé, le test échouera après une temporisation. Vous pouvez également utiliserf.completeExceptionally
pour échouer tôt.la source
Il existe de nombreuses réponses ici, mais la plus simple consiste simplement à créer un CompletableFuture terminé et à l'utiliser:
Donc dans mon test:
Je m'assure juste que toutes ces choses sont appelées de toute façon. Cette technique fonctionne si vous utilisez ce code:
Il se faufilera à travers lui car tous les CompletableFutures sont terminés!
la source
Évitez de tester avec des threads parallèles chaque fois que vous le pouvez (ce qui est la plupart du temps). Cela ne fera que rendre vos tests floconneux (parfois réussir, parfois échouer).
Ce n'est que lorsque vous devez appeler une autre bibliothèque / système, que vous devrez peut-être attendre sur d'autres threads, dans ce cas, utilisez toujours la bibliothèque Awaitility à la place de
Thread.sleep()
.N'appelez jamais simplement
get()
oujoin()
dans vos tests, sinon vos tests pourraient s'exécuter indéfiniment sur votre serveur CI au cas où l'avenir ne se terminerait jamais. Affirmez toujours d'isDone()
abord dans vos tests avant d'appelerget()
. Pour CompletionStage, c'est.toCompletableFuture().isDone()
.Lorsque vous testez une méthode non bloquante comme celle-ci:
alors vous ne devez pas simplement tester le résultat en passant un Future terminé dans le test, vous devez également vous assurer que votre méthode
doSomething()
ne bloque pas en appelantjoin()
ouget()
. Ceci est particulièrement important si vous utilisez un framework non bloquant.Pour ce faire, testez avec un futur non terminé que vous définissez comme terminé manuellement:
De cette façon, si vous ajoutez
future.join()
à doSomething (), le test échouera.Si votre service utilise un ExecutorService tel que dans
thenApplyAsync(..., executorService)
, alors dans vos tests injectez un ExecutorService à un seul thread, tel que celui de goyave:Si votre code utilise le forkJoinPool tel que
thenApplyAsync(...)
, réécrivez le code pour utiliser un ExecutorService (il existe de nombreuses bonnes raisons) ou utilisez Awaitility.Pour raccourcir l'exemple, j'ai fait de BarService un argument de méthode implémenté en tant que lambda Java8 dans le test, généralement ce serait une référence injectée dont vous vous moqueriez.
la source
Je préfère utiliser attendre et notifier. C'est simple et clair.
Fondamentalement, nous devons créer une référence de tableau finale, à utiliser à l'intérieur d'une classe interne anonyme. Je préfère créer un booléen [], car je peux mettre une valeur à contrôler si nous devons attendre (). Lorsque tout est terminé, nous libérons simplement l'asyncExecuted.
la source
Pour tous les utilisateurs de Spring, voici comment je fais habituellement mes tests d'intégration de nos jours, où le comportement asynchrone est impliqué:
Déclenchez un événement d'application dans le code de production lorsqu'une tâche asynchrone (telle qu'un appel d'E / S) est terminée. La plupart du temps, cet événement est de toute façon nécessaire pour gérer la réponse de l'opération asynchrone en production.
Avec cet événement en place, vous pouvez ensuite utiliser la stratégie suivante dans votre scénario de test:
Pour décomposer cela, vous aurez d'abord besoin d'une sorte d'événement de domaine à déclencher. J'utilise un UUID ici pour identifier la tâche qui s'est terminée, mais vous êtes bien sûr libre d'utiliser quelque chose d'autre tant qu'il est unique.
(Notez que les extraits de code suivants utilisent également des annotations Lombok pour se débarrasser du code de plaque de chaudière)
Le code de production lui-même ressemble alors généralement à ceci:
Je peux ensuite utiliser un ressort
@EventListener
pour attraper l'événement publié dans le code de test. L'écouteur d'événements est un peu plus impliqué, car il doit gérer deux cas de manière sécurisée pour les threads:A
CountDownLatch
est utilisé pour le deuxième cas, comme mentionné dans d'autres réponses ici. Notez également que l'@Order
annotation de la méthode du gestionnaire d'événements garantit que cette méthode du gestionnaire d'événements est appelée après tout autre écouteur d'événements utilisé en production.La dernière étape consiste à exécuter le système testé dans un scénario de test. J'utilise un test SpringBoot avec JUnit 5 ici, mais cela devrait fonctionner de la même manière pour tous les tests utilisant un contexte Spring.
Notez que contrairement à d'autres réponses ici, cette solution fonctionnera également si vous exécutez vos tests en parallèle et que plusieurs threads exercent le code asynchrone en même temps.
la source
Si vous voulez tester la logique, ne la testez pas de manière asynchrone.
Par exemple pour tester ce code qui fonctionne sur les résultats d'une méthode asynchrone.
Dans le test, simulez la dépendance avec une implémentation synchrone. Le test unitaire est complètement synchrone et s'exécute en 150 ms.
Vous ne testez pas le comportement asynchrone mais vous pouvez tester si la logique est correcte.
la source