Comment dois-je tester le code threadé?

Jusqu’à présent, j’ai évité le cauchemar qui met à l’épreuve le code multithread, car il semble trop exagéré. J’aimerais savoir comment les gens ont testé le code qui repose sur des threads pour une exécution réussie, ou comment les gens ont testé ces types de problèmes qui apparaissent uniquement lorsque deux threads interagissent d’une manière donnée?

Cela semble être un problème très important pour les programmeurs d’aujourd’hui, il serait utile de mettre en commun nos connaissances sur ce sujet.

Regardez, il n’y a pas de moyen facile de le faire. Je travaille sur un projet insortingnsèquement multithreadé. Les événements arrivent du système d’exploitation et je dois les traiter simultanément.

La façon la plus simple de tester des codes d’application complexes et multithread est la suivante: si elle est trop complexe à tester, vous vous trompez. Si vous avez une seule instance qui a plusieurs threads qui agissent sur elle, et que vous ne pouvez pas tester les situations où ces threads se chevauchent, votre conception doit être refaite. C’est aussi simple et complexe que cela.

Il existe de nombreuses manières de programmer le multithreading pour éviter que les threads ne traversent des instances en même temps. Le plus simple est de rendre tous vos objects immuables. Bien sûr, ce n’est généralement pas possible. Vous devez donc identifier les endroits dans votre conception où les threads interagissent avec la même instance et réduisent le nombre de ces emplacements. Ce faisant, vous isolez quelques classes dans lesquelles le multithreading se produit, réduisant ainsi la complexité globale du test de votre système.

Mais vous devez vous rendre compte que même en faisant cela, vous ne pouvez toujours pas tester chaque situation où deux threads se chevauchent. Pour ce faire, vous devez exécuter deux threads simultanément dans le même test, puis contrôler exactement les lignes qu’ils exécutent à un moment donné. Le mieux que vous puissiez faire est de simuler cette situation. Mais cela pourrait vous obliger à coder spécifiquement pour les tests, et au mieux un demi-pas vers une véritable solution.

La meilleure façon de tester le code pour les problèmes de thread est probablement l’parsing statique du code. Si votre code threadé ne suit pas un ensemble fini de modèles sécurisés, vous risquez d’avoir un problème. Je pense que l’parsing de code dans VS contient certaines connaissances sur le threading, mais probablement pas beaucoup.

Regardez, en l’état actuel des choses (et le sera probablement pour le moment), le meilleur moyen de tester les applications multithread est de réduire autant que possible la complexité du code threadé. Minimiser les zones où les threads interagissent, tester au mieux et utiliser une parsing de code pour identifier les zones dangereuses.

Ça fait un moment que cette question a été publiée, mais on ne répond toujours pas …

La réponse de kleolb02 est bonne. Je vais essayer d’entrer dans plus de détails.

Il y a un moyen que je pratique pour le code C #. Pour les tests unitaires, vous devez être capable de programmer des tests reproductibles , ce qui constitue le plus grand défi du code multithread. Ma réponse vise donc à forcer le code asynchrone dans un faisceau de test, qui fonctionne de manière synchrone .

C’est une idée du livre de Gerard Meszardos ” xUnit Test Patterns ” et s’appelle “Humble Object” (p. 695): Vous devez séparer le code logique de base et tout ce qui sent le code asynchrone les uns des autres. Cela se traduirait par une classe pour la logique de base, qui fonctionne de manière synchrone .

Cela vous permet de tester le code logique de base de manière synchrone . Vous avez un contrôle absolu sur le timing des appels que vous effectuez sur la logique principale et pouvez donc faire des tests reproductibles . Et ceci est votre gain de séparer la logique de base et la logique asynchrone.

Cette logique de base doit être contournée par une autre classe, chargée de recevoir les appels à la logique principale de manière asynchrone et de les déléguer à la logique principale. Le code de production n’accède à la logique de base que par cette classe. Parce que cette classe ne devrait déléguer que des appels, c’est une classe très “stupide” sans beaucoup de logique. Vous pouvez donc garder vos tests unitaires pour cette classe de travail asynchrone au minimum.

Tout ce qui précède (test d’interaction entre les classes) sont des tests de composants. Dans ce cas également, vous devriez pouvoir contrôler le timing de manière absolue, si vous vous en tenez au modèle “Humble Object”.

Un dur en effet! Dans mes tests unitaires (C ++), je l’ai décomposé en plusieurs catégories sur le modèle de concurrence utilisé:

  1. Tests unitaires pour les classes qui fonctionnent dans un seul thread et ne sont pas compatibles avec le thread – facile, testez comme d’habitude.

  2. Les tests unitaires pour les objects Monitor (ceux qui exécutent des méthodes synchronisées dans le thread de contrôle des appelants) qui exposent une API publique synchronisée – instancient plusieurs threads simulés qui exercent l’API. Construisez des scénarios qui exercent des conditions internes de l’object passif. Incluez un test plus long qui résiste à plusieurs threads pendant une longue période. Ce n’est pas scientifique, je le sais, mais cela renforce la confiance.

  3. Tests unitaires pour les objects actifs (ceux qui encapsulent leurs propres threads ou threads de contrôle) – similaires à # 2 ci-dessus avec des variations en fonction de la conception de la classe. L’API publique peut être bloquante ou non bloquante, les appelants peuvent obtenir des contrats à terme, les données peuvent arriver dans les files d’attente ou doivent être retirées. Il y a beaucoup de combinaisons possibles ici; boîte blanche loin. Encore besoin de plusieurs threads simulés pour faire des appels à l’object en cours de test.

En aparté:

Dans la formation des développeurs internes que je fais, j’enseigne les piliers de la concurrence et ces deux modèles en tant que cadre principal pour réfléchir et décomposer les problèmes de concurrence. Il y a évidemment des concepts plus avancés, mais j’ai trouvé que cet ensemble de bases aide les ingénieurs à restr à l’écart de la soupe. Cela conduit également à un code qui est plus testable en unités, comme décrit ci-dessus.

J’ai rencontré ce problème plusieurs fois au cours des dernières années lors de la rédaction de code de gestion des threads pour plusieurs projets. Je fournis une réponse tardive car la plupart des autres réponses, tout en proposant 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 tous les problèmes de conception de code, mais je discute également des tests unitaires.

Ecriture de code multithread testable

La première chose à faire est de séparer votre code de traitement des threads 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é sous forme de code unique, et la seule chose que le code multithread est de faire 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 pénètreront dans la production, seront difficiles à reproduire même en production et causeront donc les plus gros problèmes. Pour cette raison, l’approche de codage standard consistant à écrire rapidement le code puis à le déboguer jusqu’à ce qu’il fonctionne est une mauvaise idée pour du code multithread; Il en résultera un code où les bogues faciles seront corrigés et où les bogues dangereux seront toujours présents.

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 traitement des threads devrait ê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 sans écrire beaucoup de bogues. , si vous comprenez le filetage, prenez votre temps et faites attention.

Ecriture des tests unitaires pour le code multithread

Une fois que le code multithread est écrit le plus soigneusement possible, il est toujours intéressant d’écrire des tests pour ce code. Le principal objective des tests n’est pas tant de tester les bogues de condition de course fortement dépendants du temps – il est impossible de tester ces conditions de course de manière répétée .

Pour tester correctement le comportement de locking correct, un test doit démarrer plusieurs threads. Pour que le test soit reproductible, nous voulons que les interactions entre les threads se produisent dans un ordre prévisible. Nous ne voulons pas synchroniser en externe les threads dans le test, car cela masquerait les bogues pouvant survenir en production où les threads ne sont pas synchronisés en externe. Cela laisse des délais de synchronisation pour la synchronisation des threads, technique que j’ai utilisée avec succès chaque fois que je devais écrire des tests de code multithread.

Si les délais sont trop courts, le test devient fragile, car des différences temporelles mineures, par exemple entre différentes machines sur lesquelles les tests peuvent être exécutés, peuvent provoquer l’arrêt du chronométrage et l’échec du test. Ce que je fais généralement, c’est démarrer avec des retards qui provoquent des échecs de test, augmenter les délais pour que le test passe de manière fiable sur ma machine de développement, puis doubler les délais pour que le test puisse transmettre d’autres machines. Cela signifie que le test prendra une quantité de temps macroscopique, bien que, selon mon expérience, une conception de test minutieuse puisse limiter ce temps à pas plus d’une douzaine de secondes. Étant donné que vous ne devriez pas avoir beaucoup d’endroits nécessitant un code de coordination des threads dans votre application, cela devrait être acceptable pour votre suite de tests.

Enfin, gardez une trace du nombre de bogues détectés par votre test. Si votre test a une couverture de code de 80%, vous pouvez vous attendre à ce qu’il détecte environ 80% de vos bogues. Si votre test est bien conçu, mais ne détecte aucun bogue, il y a de fortes chances que vous n’ayez pas de bogues supplémentaires qui n’apparaissent que dans la production. Si le test détecte un ou deux bugs, vous pourriez quand même avoir de la chance. Au-delà de cela, et vous voudrez peut-être envisager une révision attentive ou même une réécriture complète de votre code de traitement des threads, car il est probable que le code contienne toujours des bogues cachés qui seront très difficiles à trouver difficile à corriger alors.

J’ai aussi eu de sérieux problèmes pour tester du code multithread. Ensuite, j’ai trouvé une solution vraiment cool dans “xUnit Test Patterns” de Gerard Meszaros. Le modèle qu’il décrit s’appelle object Humble .

Fondamentalement, il décrit comment vous pouvez extraire la logique dans un composant distinct, facile à tester, découplé de son environnement. Après avoir testé cette logique, vous pouvez tester le comportement compliqué (multi-threading, exécution asynchrone, etc …)

Il existe quelques outils assez bons. Voici un résumé de certaines des Java.

Parmi les bons outils d’parsing statique figurent FindBugs (donne quelques conseils utiles), JLint , Java Pathfinder (JPF & JPF2) et Bogor .

MultithreadedTC est un bon outil d’parsing dynamic (intégré à JUnit) où vous devez configurer vos propres scénarios de test.

ConTest d’IBM Research est intéressant. Il instruit votre code en insérant toutes sortes de comportements de modification de thread (par exemple, le sumil et le rendement) pour essayer de découvrir des bogues de manière aléatoire.

SPIN est un outil très intéressant pour modéliser vos composants Java (et autres), mais vous devez disposer d’un cadre utile. Il est difficile à utiliser tel quel, mais extrêmement puissant si vous savez l’utiliser. Quelques outils utilisent SPIN sous le capot.

Le multithreadedTC est probablement le plus courant, mais certains des outils d’parsing statique énumérés ci-dessus méritent d’être examinés.

J’en ai fait beaucoup, et oui ça craint.

Quelques conseils:

  • GroboUtils pour exécuter plusieurs threads de test
  • alphaWorks ConTest pour instrumenter les classes afin de faire varier les liaisons entre les itérations
  • Créez un champ throwable et vérifiez-le dans tearDown (voir le listing 1). Si vous attrapez une mauvaise exception dans un autre thread, assignez-la à throwable.
  • J’ai créé la classe utils dans le Listing 2 et je l’ai trouvée inestimable, en particulier waitForVerify et waitForCondition, ce qui augmentera considérablement les performances de vos tests.
  • Utilisez AtomicBoolean dans vos tests. Il est compatible avec les threads et vous aurez souvent besoin d’un type de référence final pour stocker les valeurs des classes de rappel et autres. Voir exemple dans le listing 3.
  • Veillez à toujours laisser un délai d’attente à votre test (par exemple, @Test(timeout=60*1000) ), car les tests de concurrence peuvent parfois se bloquer pour toujours lorsqu’ils sont brisés.

Listing 1:

 @After public void tearDown() { if ( throwable != null ) throw throwable; } 

Listing 2:

 import static org.junit.Assert.fail; import java.io.File; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.Random; import org.apache.commons.collections.Closure; import org.apache.commons.collections.Predicate; import org.apache.commons.lang.time.StopWatch; import org.easymock.EasyMock; import org.easymock.classextension.internal.ClassExtensionHelper; import static org.easymock.classextension.EasyMock.*; import ca.digitalrapids.io.DRFileUtils; /** * Various utilities for testing */ public abstract class DRTestUtils { static private Random random = new Random(); /** Calls {@link #waitForCondition(Integer, Integer, Predicate, Ssortingng)} with * default max wait and check period values. */ static public void waitForCondition(Predicate predicate, Ssortingng errorMessage) throws Throwable { waitForCondition(null, null, predicate, errorMessage); } /** Blocks until a condition is true, throwing an {@link AssertionError} if * it does not become true during a given max time. * @param maxWait_ms max time to wait for true condition. Optional; defaults * to 30 * 1000 ms (30 seconds). * @param checkPeriod_ms period at which to try the condition. Optional; defaults * to 100 ms. * @param predicate the condition * @param errorMessage message use in the {@link AssertionError} * @throws Throwable on {@link AssertionError} or any other exception/error */ static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, Predicate predicate, Ssortingng errorMessage) throws Throwable { waitForCondition(maxWait_ms, checkPeriod_ms, predicate, new Closure() { public void execute(Object errorMessage) { fail((Ssortingng)errorMessage); } }, errorMessage); } /** Blocks until a condition is true, running a closure if * it does not become true during a given max time. * @param maxWait_ms max time to wait for true condition. Optional; defaults * to 30 * 1000 ms (30 seconds). * @param checkPeriod_ms period at which to try the condition. Optional; defaults * to 100 ms. * @param predicate the condition * @param closure closure to run * @param argument argument for closure * @throws Throwable on {@link AssertionError} or any other exception/error */ static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, Predicate predicate, Closure closure, Object argument) throws Throwable { if ( maxWait_ms == null ) maxWait_ms = 30 * 1000; if ( checkPeriod_ms == null ) checkPeriod_ms = 100; StopWatch stopWatch = new StopWatch(); stopWatch.start(); while ( !predicate.evaluate(null) ) { Thread.sleep(checkPeriod_ms); if ( stopWatch.getTime() > maxWait_ms ) { closure.execute(argument); } } } /** Calls {@link #waitForVerify(Integer, Object)} with null * for {@code maxWait_ms} */ static public void waitForVerify(Object easyMockProxy) throws Throwable { waitForVerify(null, easyMockProxy); } /** Repeatedly calls {@link EasyMock#verify(Object[])} until it succeeds, or a * max wait time has elapsed. * @param maxWait_ms Max wait time. null defaults to 30s. * @param easyMockProxy Proxy to call verify on * @throws Throwable */ static public void waitForVerify(Integer maxWait_ms, Object easyMockProxy) throws Throwable { if ( maxWait_ms == null ) maxWait_ms = 30 * 1000; StopWatch stopWatch = new StopWatch(); stopWatch.start(); for(;;) { try { verify(easyMockProxy); break; } catch (AssertionError e) { if ( stopWatch.getTime() > maxWait_ms ) throw e; Thread.sleep(100); } } } /** Returns a path to a directory in the temp dir with the name of the given * class. This is useful for temporary test files. * @param aClass test class for which to create dir * @return the path */ static public Ssortingng getTestDirPathForTestClass(Object object) { Ssortingng filename = object instanceof Class ? ((Class)object).getName() : object.getClass().getName(); return DRFileUtils.getTempDir() + File.separator + filename; } static public byte[] createRandomByteArray(int bytesLength) { byte[] sourceBytes = new byte[bytesLength]; random.nextBytes(sourceBytes); return sourceBytes; } /** Returns true if the given object is an EasyMock mock object */ static public boolean isEasyMockMock(Object object) { try { InvocationHandler invocationHandler = Proxy .getInvocationHandler(object); return invocationHandler.getClass().getName().contains("easymock"); } catch (IllegalArgumentException e) { return false; } } } 

Listing 3:

 @Test public void testSomething() { final AtomicBoolean called = new AtomicBoolean(false); subject.setCallback(new SomeCallback() { public void callback(Object arg) { // check arg here called.set(true); } }); subject.run(); assertTrue(called.get()); } 

L’attente peut également être utile pour vous aider à écrire des tests unitaires déterministes. Il vous permet d’attendre qu’un état quelque part dans votre système soit mis à jour. Par exemple:

 await().untilCall( to(myService).myMethod(), greaterThan(3) ); 

ou

 await().atMost(5,SECONDS).until(fieldIn(myObject).ofType(int.class), equalTo(1)); 

Il a également un soutien Scala et Groovy.

 await until { something() > 4 } // Scala example 

Tester le code MT pour l’exactitude est, comme déjà indiqué, un problème assez difficile. En fin de compte, cela revient à vous assurer qu’il n’y a pas de courses de données synchronisées de manière incorrecte dans votre code. Le problème est qu’il existe une infinité de possibilités d’exécution de threads (interleavings) sur lesquelles vous n’avez pas beaucoup de contrôle (lisez bien cet article). Dans des scénarios simples, il peut être possible de prouver l’exactitude en raisonnant, mais ce n’est généralement pas le cas. Surtout si vous voulez éviter / minimiser la synchronisation et ne pas opter pour l’option de synchronisation la plus évidente / la plus simple.

Une approche que je suis consiste à écrire un code de test hautement simultané afin de rendre possible des courses de données potentiellement non détectées. Et puis je lance ces tests pendant un certain temps 🙂 Une fois, je suis tombé sur une conversation où des informaticiens présentaient un outil du même type (conception aléatoire des tests à partir des spécifications, puis exécution simultanée, vérification des invariants définis) être cassé).

Au fait, je pense que cet aspect du test du code MT n’a pas été mentionné ici: identifiez les invariants du code que vous pouvez vérifier aléatoirement. Malheureusement, trouver ces invariants est également un problème difficile. De plus, ils peuvent ne pas être stockés tout le temps pendant l’exécution, vous devez donc trouver / appliquer des points d’exécution où vous pouvez vous attendre à ce qu’ils soient vrais. Amener l’exécution du code dans un tel état est également un problème difficile (et peut en soi engendrer des problèmes de concurrence).

Quelques liens intéressants à lire:

  • Entrelacement déterministe : un cadre qui permet de forcer certains entrelacs de fils et de vérifier les invariants
  • jMock Blitzer : synchronisation du test de stress
  • assertConcurrent : version JUnit de la synchronisation des tests de stress
  • Test du code concurrent : Bref aperçu des deux méthodes principales de la force brute (test de contrainte) ou déterministe (aller pour les invariants)

Le test Fuzz est une autre façon de tester (en quelque sorte) le code threadé et les systèmes très complexes en général. Ce n’est pas génial, et il ne trouvera pas tout, mais il sera probablement utile et simple à réaliser.

Citation:

Le test de fuzz ou le fuzzing est une technique de test de logiciel qui fournit des données aléatoires (“fuzz”) aux entrées d’un programme. Si le programme échoue (par exemple, en plantant ou en échouant aux assertions de code intégrées), les défauts peuvent être notés. Le grand avantage des tests fuzz est que la conception des tests est extrêmement simple et exempte d’idées préconçues sur le comportement du système.

Les tests Fuzz sont souvent utilisés dans les grands projets de développement de logiciels qui utilisent des tests de boîte noire. Ces projets ont généralement un budget pour développer des outils de test, et le test de fuzz est l’une des techniques qui offre un rapport bénéfice / coût élevé.

Cependant, le test de fuzz ne remplace pas le test exhaustif ou les méthodes formelles: il ne peut fournir qu’un échantillon aléatoire du comportement du système et, dans de nombreux cas, réussir un test fuzz peut uniquement démontrer qu’un logiciel gère les exceptions sans se bloquer. se comporter correctement Ainsi, le test de fuzz ne peut être considéré que comme un outil de recherche de bogues plutôt que comme une garantie de qualité.

Pete Goodliffe a une série sur les tests unitaires du code threadé .

C’est dur. Je prends la voie la plus simple et essaye de garder le code de filetage abstrait du test réel. Pete mentionne que la façon dont je le fais est fausse, mais j’ai soit bien compris, soit j’ai eu de la chance.

Pour Java, consultez le chapitre 12 de JCIP . Il existe des exemples concrets d’écriture de tests unitaires déterministes et multithread pour au moins tester l’exactitude et les invariants du code concurrent.

“Prouver” la sécurité des threads avec des tests unitaires est beaucoup plus difficile. Je crois que cela est mieux servi par des tests d’intégration automatisés sur diverses plates-formes / configurations.

Je gère les tests unitaires des composants filetés de la même manière que je gère les tests unitaires, c’est-à-dire avec l’inversion des structures de contrôle et d’isolation. Je développe dans l’arène .Net et en dehors de la boîte, le threading (entre autres choses) est très difficile (je dirais presque impossible) d’isoler complètement.

J’ai donc écrit des wrappers qui ressemblent à ceci (simplifié):

 public interface IThread { void Start(); ... } public class ThreadWrapper : IThread { private readonly Thread _thread; public ThreadWrapper(ThreadStart threadStart) { _thread = new Thread(threadStart); } public Start() { _thread.Start(); } } public interface IThreadingManager { IThread CreateThread(ThreadStart threadStart); } public class ThreadingManager : IThreadingManager { public IThread CreateThread(ThreadStart threadStart) { return new ThreadWrapper(threadStart) } } 

À partir de là, je peux facilement injecter IThreadingManager dans mes composants et utiliser le cadre d’isolement de mon choix pour que le thread se comporte comme prévu pendant le test.

Cela a jusqu’ici fonctionné très bien pour moi, et j’utilise la même approche pour le pool de threads, les choses dans System.Environment, Sleep etc. etc.

J’aime écrire deux ou plusieurs méthodes de test à exécuter sur des threads parallèles, et chacune effectue des appels dans l’object testé. J’ai utilisé les appels Sleep () pour coordonner l’ordre des appels provenant des différents threads, mais ce n’est pas vraiment fiable. C’est aussi beaucoup plus lent parce que vous devez dormir assez longtemps pour que le timing fonctionne normalement.

J’ai trouvé la bibliothèque multi – threadée TC Java du même groupe qui a écrit FindBugs. Il vous permet de spécifier l’ordre des événements sans utiliser Sleep (), et c’est fiable. Je n’ai pas encore essayé.

La plus grande limite à cette approche est qu’elle ne vous permet de tester que les scénarios susceptibles de causer des problèmes. Comme d’autres l’ont dit, vous devez vraiment isoler votre code multithread dans un petit nombre de classes simples pour pouvoir espérer les tester en profondeur.

Une fois que vous avez soigneusement testé les scénarios que vous prévoyez de causer des problèmes, un test non scientifique qui lance plusieurs requêtes simultanées pendant un certain temps est un bon moyen de détecter les problèmes inattendus.

Mise à jour: J’ai joué un peu avec la bibliothèque multi-threadée TC Java, et ça marche bien. J’ai également porté certaines de ses fonctionnalités sur une version .NET que j’appelle TickingTest .

Regardez ma réponse à

Conception d’une classe de test pour une barrière personnalisée

Il est biaisé vers Java mais a un résumé raisonnable des options.

En résumé, (IMO), ce n’est pas l’utilisation d’un cadre sophistiqué qui garantira l’exactitude, mais comment vous allez concevoir votre code multithread. Diviser les préoccupations (access simultané et fonctionnalité) consortingbue grandement à renforcer la confiance. Un logiciel orienté object orienté par tests explique mieux que moi certaines options.

L’parsing statique et les méthodes formelles (voir, Concurrence: modèles d’état et programmes Java ) sont une option mais je les ai trouvées d’une utilité limitée dans le développement commercial.

N’oubliez pas que tous les tests de type charge / trempage sont rarement garantis pour mettre en évidence des problèmes.

Bonne chance!

Je viens de découvrir (pour Java) un outil appelé Threadsafe. Il s’agit d’un outil d’parsing statique très similaire à findbugs, mais spécifiquement conçu pour détecter les problèmes de multithreading. Ce n’est pas un remplacement pour les tests mais je peux le recommander dans le cadre de l’écriture de Java multi-threads fiables.

Il intercepte même des problèmes potentiels très subtils autour de la subsomption de classes, l’access à des objects non sécurisés via des classes simultanées et le repérage de modificateurs volatils manquants lors de l’utilisation du paradigme de locking à double vérification.

Si vous écrivez en Java multithread, essayez-le .

L’article suivant suggère 2 solutions. Envelopper un sémaphore (CountDownLatch) et append des fonctionnalités telles que l’externalisation des données du thread interne. Une autre façon d’atteindre cet objective consiste à utiliser le pool de threads (voir Points d’intérêt).

Sprinkler – Objet de synchronisation avancé

J’ai passé la majeure partie de la semaine dernière dans une bibliothèque universitaire à étudier le débogage de code concurrent. Le problème central est que le code concurrent est non déterministe. En règle générale, le débogage universitaire est tombé dans l’un des trois camps suivants:

  1. Event-trace / replay. Cela nécessite un moniteur d’événements, puis examine les événements envoyés. Dans un cadre de test, cela impliquerait l’envoi manuel des événements dans le cadre d’un test, puis des revues post mortem.
  2. Scriptable. C’est là que vous interagissez avec le code en cours d’exécution avec un ensemble de déclencheurs. “Sur x> toto, baz ()”. Cela pourrait être interprété dans un cadre UT où vous avez un système d’exécution qui déclenche un test donné dans certaines conditions.
  3. Interactif. Cela ne fonctionnera évidemment pas dans une situation de test automatique. 😉

Maintenant, comme les commentateurs l’ont remarqué, vous pouvez concevoir votre système simultané dans un état plus déterministe. Cependant, si vous ne le faites pas correctement, vous revenez à la conception d’un système séquentiel.

Ma suggestion serait de mettre l’accent sur un protocole de conception très ssortingct concernant ce qui est mis en file et ce qui ne l’est pas. Si vous contraignez votre interface de sorte qu’il y ait un minimum de dépendances entre les éléments, c’est beaucoup plus facile.

Bonne chance et continuez à travailler sur le problème.

J’ai eu la malheureuse tâche de tester le code threadé et ce sont certainement les tests les plus difficiles que j’ai jamais écrits.

Lors de l’écriture de mes tests, j’ai utilisé une combinaison de delegates et d’événements. Fondamentalement, il s’agit d’utiliser des événements PropertyNotifyChanged avec un WaitCallback ou une sorte de ConditionalWaiter qui interroge.

Je ne suis pas sûr que ce soit la meilleure approche, mais cela a fonctionné pour moi.

Pour le code J2E, j’ai utilisé SilkPerformer, LoadRunner et JMeter pour tester les access simultanés des threads. Ils font tous la même chose. Fondamentalement, ils vous fournissent une interface relativement simple pour administrer leur version du serveur proxy, afin d’parsingr le stream de données TCP / IP et de simuler plusieurs utilisateurs effectuant des requêtes simultanées sur votre serveur d’applications. Le serveur proxy peut vous permettre d’parsingr les requêtes effectuées, en présentant la page entière et l’URL envoyées au serveur, ainsi que la réponse du serveur après le traitement de la demande.

Vous pouvez trouver des bogues en mode http non sécurisé, où vous pouvez au moins parsingr les données de formulaire envoyées et les modifier systématiquement pour chaque utilisateur. Mais les vrais tests sont lorsque vous exécutez en https (couches de socket sécurisées). Ensuite, vous devez également modifier systématiquement les données de session et de cookie, ce qui peut être un peu plus compliqué.

Le meilleur bogue que j’ai jamais trouvé, lors du test de concomitance, était lorsque j’ai découvert que le développeur utilisait le nettoyage de la mémoire Java pour fermer la demande de connexion établie lors de la connexion au serveur LDAP. aux sessions des autres utilisateurs et aux résultats très confus, en essayant d’parsingr ce qui s’est passé quand le serveur a été mis à genoux, à peine capable d’effectuer une transaction, toutes les quelques secondes.

Au bout du compte, vous ou quelqu’un devrez probablement boucler votre boucle et parsingr le code pour détecter des erreurs, comme celle que je viens de mentionner. Et une discussion ouverte entre les départements, comme celle qui s’est produite lorsque nous avons dévoilé le problème décrit ci-dessus, est très utile. Mais ces outils sont la meilleure solution pour tester le code multi-thread. JMeter est open source. SilkPerformer et LoadRunner sont propriétaires. Si vous voulez vraiment savoir si votre application est thread-safe, c’est comme ça que les grands garçons le font. Je l’ai fait professionnellement pour de très grandes entresockets, alors je ne devine pas. Je parle de mon expérience personnelle.

Un mot de prudence: il faut un certain temps pour comprendre ces outils. Il ne s’agira pas simplement d’installer le logiciel et de lancer l’interface graphique, sauf si vous avez déjà été exposé à une programmation multithread. J’ai essayé d’identifier les trois catégories critiques de domaines à comprendre (formulaires, données de session et de cookies), en espérant qu’au moins commencer par comprendre ces sujets vous aidera à vous concentrer sur des résultats rapides, au lieu de devoir lire les toute la documentation.

La concurrence est une interaction complexe entre le modèle de mémoire, le matériel, les caches et notre code. Dans le cas de Java, au moins ces tests ont été en partie traités principalement par jcstress . Les créateurs de cette bibliothèque sont connus pour être les auteurs de nombreuses fonctionnalités de concurrence JVM, GC et Java.

Mais même cette bibliothèque nécessite une bonne connaissance de la spécification du modèle de mémoire Java afin que nous sachions exactement ce que nous testons. Mais je pense que l’accent de cet effort est mircobenchmarks. Des applications métier pas énormes.

Si vous testez de nouveaux threads simples (exécutables) .run () Vous pouvez alors simuler Thread pour exécuter le programme exécutable séquentiellement

Par exemple si le code de l’object testé appelle un nouveau thread comme celui-ci

 Class TestedClass { public void doAsychOp() { new Thread(new myRunnable()).start(); } } 

Puis moquant les nouveaux threads et exécuter l’argument exécutable de manière séquentielle peut aider

 @Mock private Thread threadMock; @Test public void myTest() throws Exception { PowerMockito.mockStatic(Thread.class); //when new thread is created execute runnable immediately PowerMockito.whenNew(Thread.class).withAnyArguments().then(new Answer() { @Override public Thread answer(InvocationOnMock invocation) throws Throwable { // immediately run the runnable Runnable runnable = invocation.getArgumentAt(0, Runnable.class); if(runnable != null) { runnable.run(); } return threadMock;//return a mock so Thread.start() will do nothing } }); TestedClass testcls = new TestedClass() testcls.doAsychOp(); //will invoke myRunnable.run in current thread //.... check expected } 

(si possible) n’utilisez pas de threads, utilisez des acteurs / objects actifs. Facile à tester.

Vous pouvez utiliser EasyMock.makeThreadSafe pour créer une instance de test