J'ai rencontré ce problème plusieurs fois ces dernières années lors de l'écriture de code de gestion de threads pour plusieurs projets. Je fournis une réponse tardive car la plupart des autres réponses, tout en fournissant des alternatives, ne répondent pas réellement à la question sur les tests. Ma réponse s'adresse aux cas où il n'y a pas d'alternative au code multithread; Je couvre les problèmes de conception de code pour être complet, mais je discute également des tests unitaires.
Écriture de code multithread testable
La première chose à faire est de séparer le code de gestion de votre thread de production de tout le code qui effectue le traitement des données. De cette façon, le traitement des données peut être testé en tant que code à thread unique, et la seule chose que fait le code multithread est de coordonner les threads.
La deuxième chose à retenir est que les bogues dans le code multithread sont probabilistes; les bogues qui se manifestent le moins fréquemment sont les bogues qui se faufileront en production, seront difficiles à reproduire même en production, et causeront ainsi les plus gros problèmes. Pour cette raison, l'approche de codage standard consistant à écrire le code rapidement puis à le déboguer jusqu'à ce qu'il fonctionne est une mauvaise idée pour le code multithread; il en résultera du code où les bogues faciles sont corrigés et les bogues dangereux sont toujours là.
Au lieu de cela, lors de l'écriture de code multithread, vous devez écrire le code avec l'attitude que vous allez éviter d'écrire les bogues en premier lieu. Si vous avez correctement supprimé le code de traitement des données, le code de gestion des threads doit être suffisamment petit - de préférence quelques lignes, au pire quelques dizaines de lignes - pour que vous puissiez l'écrire sans écrire de bogue, et certainement sans écrire de nombreux bogues. , si vous comprenez le filetage, prenez votre temps et faites attention.
Écriture de tests unitaires pour du code multithread
Une fois que le code multithread est écrit aussi soigneusement que possible, il vaut toujours la peine d'écrire des tests pour ce code. Le but principal des tests n'est pas tant de tester des bogues de conditions de course très dépendants du temps - il est impossible de tester de telles conditions de course de manière répétée - mais plutôt de tester que votre stratégie de verrouillage pour empêcher de tels bogues permet à plusieurs threads d'interagir comme prévu .
Pour tester correctement le comportement de verrouillage correct, un test doit démarrer plusieurs threads. Pour rendre le test reproductible, nous voulons que les interactions entre les threads se produisent dans un ordre prévisible. Nous ne voulons pas synchroniser les threads en externe dans le test, car cela masquera les bogues qui pourraient survenir en production lorsque les threads ne sont pas synchronisés en externe. Cela laisse l'utilisation de délais de synchronisation pour la synchronisation des threads, qui est la technique que j'ai utilisée avec succès chaque fois que j'ai dû écrire des tests de code multithread.
Si les retards sont trop courts, le test devient fragile, car des différences de synchronisation mineures - disons entre les différentes machines sur lesquelles les tests peuvent être exécutés - peuvent entraîner une interruption du chronométrage et un échec du test. Ce que j'ai généralement fait, c'est commencer avec des retards qui provoquent des échecs de test, augmenter les retards pour que le test passe de manière fiable sur ma machine de développement, puis doubler les retards au-delà de cela pour que le test ait de bonnes chances de passer sur d'autres machines. Cela signifie que le test prendra un temps macroscopique, bien que d'après mon expérience, une conception de test minutieuse peut limiter ce temps à pas plus d'une douzaine de secondes. Comme vous ne devriez pas avoir beaucoup d'endroits nécessitant du code de coordination de threads dans votre application, cela devrait être acceptable pour votre suite de tests.
Enfin, gardez une trace du nombre de bugs détectés par votre test. Si votre test a une couverture de code de 80%, on peut s'attendre à ce qu'il détecte environ 80% de vos bogues. Si votre test est bien conçu mais ne trouve aucun bogue, il y a une chance raisonnable que vous n'ayez pas de bogues supplémentaires qui n'apparaîtront qu'en production. Si le test détecte un ou deux bogues, vous pourriez toujours avoir de la chance. Au-delà de cela, et vous voudrez peut-être envisager un examen attentif ou même une réécriture complète de votre code de gestion des threads, car il est probable que le code contienne encore des bogues cachés qui seront très difficiles à trouver jusqu'à ce que le code soit en production, et très difficile à réparer alors.