Wie soll ich den Thread-Code testen?

Ich habe bisher den Albtraum vermieden, der multi-threaded Code testet, da es einfach wie ein Minenfeld aussieht. Ich möchte fragen, wie die Leute Code testen, der auf Threads für die erfolgreiche Ausführung angewiesen ist, oder wie die Leute solche Probleme getestet haben, die nur dann auftauchen, wenn zwei Threads in einer bestimmten Weise interagieren?

Dies scheint für Programmierer heute ein wirklich Schlüsselproblem zu sein, es wäre nützlich, unser Wissen über dieses eine IMHO zu bündeln.

Schau, es gibt keinen einfachen Weg, dies zu tun. Ich arbeite an einem Projekt, das von Natur aus Multithread ist. Ereignisse kommen vom Betriebssystem und ich muss sie gleichzeitig verarbeiten.

Der einfachste Weg, komplexe Testanwendungen mit mehreren Threads zu testen, ist folgender: Wenn es zu komplex ist, um es zu testen, tun Sie es falsch. Wenn Sie eine einzelne Instanz mit mehreren Threads haben, die auf sie einwirken, und Sie keine Situationen testen können, in denen sich diese Threads überschneiden, muss Ihr Design erneut erstellt werden. Es ist so einfach und so komplex wie dieses.

Es gibt viele Möglichkeiten zum Programmieren für Multithreading, die verhindern, dass Threads gleichzeitig durch Instanzen laufen. Am einfachsten ist es, alle deine Objekte unveränderlich zu machen. Natürlich ist das normalerweise nicht möglich. Sie müssen also die Stellen in Ihrem Entwurf identifizieren, an denen Threads mit der gleichen Instanz interagieren und die Anzahl dieser Stellen reduzieren. Indem Sie dies tun, isolieren Sie einige classn, in denen Multithreading tatsächlich auftritt, wodurch die Gesamtkomplexität beim Testen Ihres Systems verringert wird.

Aber Sie müssen erkennen, dass Sie selbst dann nicht jede Situation testen können, in der zwei Fäden aufeinander treten. Dazu müssten Sie im selben Test zwei Threads gleichzeitig ausführen und dann genau steuern, welche Zeilen sie gerade ausführen. Das Beste, was Sie tun können, ist diese Situation zu simulieren. Dies erfordert jedoch, dass Sie speziell für das Testen programmieren müssen, und das ist im besten Fall ein halber Schritt hin zu einer echten Lösung.

Die beste Möglichkeit, Code für Threading-Probleme zu testen, ist wahrscheinlich die statische Analyse des Codes. Wenn Ihr Thread-Code keiner endlichen Menge von Thread-sicheren Mustern folgt, haben Sie möglicherweise ein Problem. Ich glaube, Code-Analyse in VS enthält einige Kenntnisse von Threading, aber wahrscheinlich nicht viel.

Schau, wie die Dinge momentan stehen (und wahrscheinlich für eine gute Zeit stehen werden), ist der beste Weg, Multithread-Anwendungen zu testen, die Komplexität von Thread-Code so weit wie möglich zu reduzieren. Minimieren Sie Bereiche, in denen Threads interagieren, testen Sie so gut wie möglich und verwenden Sie eine Codeanalyse, um Gefahrenbereiche zu identifizieren.

Es ist eine Weile her, als diese Frage gestellt wurde, aber es wurde immer noch nicht beantwortet …

kleolb02 ‘s Antwort ist eine gute Antwort. Ich werde versuchen, mehr ins Detail zu gehen.

Es gibt einen Weg, den ich für C # -Code übe. Für Komponententests sollten Sie in der Lage sein, reproduzierbare Tests zu programmieren, was bei Multithread-Code die größte Herausforderung darstellt. Meine Antwort zielt also darauf ab, asynchronen Code in eine Testumgebung zu zwingen, die synchron arbeitet .

Es ist eine Idee aus Gerard Meszardos Buch ” xUnit Test Patterns ” und heißt “Humble Object” (S. 695): Sie müssen core-Logik-Code und alles, was wie asynchroner Code riecht, voneinander trennen. Dies würde zu einer class für die corelogik führen, die synchron arbeitet .

Dies versetzt Sie in die Lage, den corelogik-Code synchron zu testen. Sie haben absolute Kontrolle über das Timing der Aufrufe, die Sie in der corelogik ausführen, und können somit reproduzierbare Tests durchführen. Und das ist Ihr Vorteil durch die Trennung von corelogik und asynchroner Logik.

Diese corelogik muss von einer anderen class umgeben sein, die dafür verantwortlich ist, asynchrone Aufrufe der corelogik zu empfangen und diese Aufrufe an die corelogik zu delegieren . Der Produktionscode greift nur über diese class auf die corelogik zu. Da diese class nur Anrufe delegieren sollte, handelt es sich um eine sehr “dumme” class ohne viel Logik. So können Sie Ihre Unit-Tests für diese asynchrone Arbeitsklasse auf ein Minimum beschränken.

Alles darüber hinaus (Testen der Interaktion zwischen classn) sind Komponententests. Auch in diesem Fall sollten Sie absolute Kontrolle über das Timing haben, wenn Sie sich an das “Humble Object” -Muster halten.

Hart in der Tat! In meinen (C ++) Unit-Tests habe ich dies in mehrere Kategorien unterteilt, die dem Muster der Parallelität entsprechen:

  1. Komponententests für classn, die in einem einzigen Thread arbeiten und nicht Thread-bewusst sind – einfach, wie üblich testen.

  2. Unit-Tests für Monitor-Objekte (diejenigen, die synchronisierte Methoden im Steuerungs-Thread der Aufrufer ausführen), die eine synchronisierte öffentliche API offenlegen – Instanziieren mehrerer Mock-Threads, die die API ausführen. Konstruiere Szenarien, die innere Bedingungen des passiven Objekts ausüben. Fügen Sie einen längeren Test ein, der für eine lange Zeit aus mehreren Threads herauskommt. Das ist unwissenschaftlich, weiß ich, aber es schafft Vertrauen.

  3. Unit-Tests für Active-Objekte (diejenigen, die ihre eigenen Thread oder Threads der Kontrolle kapseln) – ähnlich wie oben # 2 mit Variationen je nach classn-Design. Die öffentliche API blockiert möglicherweise oder blockiert nicht, Anrufer können Futures erhalten, Daten können in Warteschlangen gelangen oder müssen aus der Warteschlange genommen werden. Hier sind viele Kombinationen möglich; weiße Box weg. Es sind immer noch mehrere Mock-Threads erforderlich, um Aufrufe an das zu testende Objekt vorzunehmen.

Nebenbei:

Im internen Entwicklertraining, das ich mache, lehre ich die Säulen der Parallelität und diese beiden Muster als den primären Rahmen für das Nachdenken über und die Zerlegung von Nebenläufigkeitsproblemen. Es gibt offensichtlich mehr fortgeschrittene Konzepte da draußen, aber ich habe festgestellt, dass diese Grundlagen dazu beitragen, die Ingenieure aus der Suppe herauszuhalten. Es führt auch zu Code, der mehr Einheit-testbar ist, wie oben beschrieben.

Ich habe dieses Problem in den letzten Jahren mehrmals gesehen, als ich Thread-Code für mehrere Projekte geschrieben habe. Ich gebe eine späte Antwort, weil die meisten anderen Antworten zwar Alternativen bieten, aber die Frage nach dem Testen nicht wirklich beantworten. Meine Antwort bezieht sich auf Fälle, in denen es keine Alternative zu Multithreading-Code gibt. Ich behandle Code-Design-Fragen zur Vollständigkeit, diskutiere aber auch Unit-Tests.

Schreiben von testbarem Multithread-Code

Das erste, was zu tun ist, trennen Sie Ihre Produktions-Thread-Handling-Code von allen Code, der eigentliche Datenverarbeitung tut. Auf diese Weise kann die Datenverarbeitung als single-threaded-Code getestet werden, und das einzige, was der Multithread-Code tut, ist die Koordination von Threads.

Die zweite Sache, die man sich merken sollte, ist, dass Bugs in Multithreading-Code probabilistisch sind; die Fehler, die sich am seltensten manifestieren, sind die Bugs, die sich in die Produktion einschleichen, selbst in der Produktion schwierig zu reproduzieren sind und somit die größten Probleme verursachen. Aus diesem Grund ist der Standardcodierungsansatz, den Code schnell zu schreiben und dann zu debuggen, bis er funktioniert, eine schlechte Idee für Multithread-Code; Es wird zu Code führen, in dem die einfachen Bugs behoben sind und die gefährlichen Bugs immer noch da sind.

Stattdessen müssen Sie beim Schreiben von Multithread-Code den Code mit der Einstellung schreiben, dass Sie das Schreiben der Fehler vermeiden. Wenn Sie den Datenverarbeitungscode ordnungsgemäß entfernt haben, sollte der Thread-Handhabungscode klein genug sein – vorzugsweise ein paar Zeilen, im schlimmsten Fall ein paar Dutzend Zeilen -, dass Sie die Möglichkeit haben, ihn zu schreiben, ohne einen Fehler zu schreiben und sicherlich ohne viele Fehler zu schreiben Wenn Sie das Threading verstehen, nehmen Sie sich Zeit und seien Sie vorsichtig.

Schreibeinheitstests für Multithread-Code

Sobald der Multithread-Code so sorgfältig wie möglich geschrieben wurde, lohnt es sich noch, Tests für diesen Code zu schreiben. Der Hauptzweck der Tests besteht nicht so sehr darin, auf stark zeitabhängige Racebedingungserrors zu testen – es ist unmöglich, diese Race-Bedingungen wiederholbar zu testen – sondern vielmehr zu testen, dass Ihre Sperrstrategie zum Verhindern solcher Bugs mehreren Threads erlaubt, wie beabsichtigt zu interagieren .

Um das korrekte Sperrverhalten richtig zu testen, muss ein Test mehrere Threads starten. Um den Test wiederholbar zu machen, wollen wir, dass die Interaktionen zwischen den Threads in einer vorhersagbaren Reihenfolge ablaufen. Wir möchten die Threads im Test nicht extern synchronisieren, da dies Fehler in der Produktion maskiert, bei denen die Threads nicht extern synchronisiert werden. Das lässt Timing Delays für die Thread-Synchronisation, die Technik, die ich erfolgreich verwendet habe, wenn ich Tests von Multithread-Code schreiben musste.

Wenn die Verzögerungen zu kurz sind, wird der Test fragil, da geringfügige Zeitunterschiede – beispielsweise zwischen verschiedenen Maschinen, auf denen die Tests ausgeführt werden können – dazu führen können, dass das Timing ausbleibt und der Test fehlschlägt. In der Regel starte ich mit Verzögerungen, die Testerrors verursachen, erhöhen die Verzögerungen, damit der Test zuverlässig auf meinem Entwicklungscomputer ausgeführt wird, und verdopple die Verzögerungen danach, sodass der Test gute Chancen hat, andere Maschinen weiterzugeben. Dies bedeutet, dass der Test eine makroskopische Zeitspanne benötigt, obwohl nach meiner Erfahrung ein sorgfältiges Testdesign diese Zeit auf nicht mehr als ein Dutzend Sekunden beschränken kann. Da in Ihrer Anwendung nicht sehr viele Bereiche Thread-Koordinationscode benötigen, sollte dies für Ihre Testsuite akzeptabel sein.

Behalte schließlich die Anzahl der Fehler im Test im Auge. Wenn Ihr Test eine 80% ige Code-Abdeckung hat, können Sie ungefähr 80% Ihrer Fehler feststellen. Wenn Ihr Test gut gestaltet ist, aber keine Fehler findet, besteht die begründete Möglichkeit, dass Sie keine zusätzlichen Fehler haben, die nur in der Produktion auftauchen. Wenn der Test einen oder zwei Fehler entdeckt, können Sie immer noch Glück haben. Darüber hinaus sollten Sie eine sorgfältige Überprüfung oder sogar eine vollständige Neufassung Ihres Thread-Handling-Codes in Betracht ziehen, da der Code wahrscheinlich immer noch versteckte Fehler enthält, die sehr schwer zu finden sind, bis der Code in Produktion ist schwer zu reparieren dann.

Ich hatte auch Probleme beim Testen von Multi-Thread-Code. Dann fand ich eine wirklich coole Lösung in “xUnit Test Patterns” von Gerard Meszaros. Das Muster, das er beschreibt, heißt Humble-Objekt .

Im Wesentlichen wird beschrieben, wie Sie die Logik in eine separate, leicht zu testende Komponente extrahieren können, die von ihrer Umgebung entkoppelt ist. Nachdem Sie diese Logik getestet haben, können Sie das komplizierte Verhalten testen (Multi-Threading, asynchrone Ausführung usw.).

Es gibt ein paar Werkzeuge, die ziemlich gut sind. Hier ist eine Zusammenfassung von einigen Java-Einträgen.

Einige gute statische Analysewerkzeuge enthalten FindBugs (gibt einige nützliche Hinweise), JLint , Java Pathfinder (JPF & JPF2) und Bogor .

MultithreadedTC ist ein ziemlich gutes dynamisches Analysewerkzeug (in JUnit integriert), wo Sie Ihre eigenen Testfälle einrichten müssen.

ConTest von IBM Research ist interessant. Es instrumentiert Ihren Code durch Einfügen aller Arten von Thread-modifizierenden Verhaltensweisen (z. B. Schlaf und Ertrag), um Fehler zufällig zu entdecken.

SPIN ist ein wirklich cooles Werkzeug für die Modellierung Ihrer Java (und anderer) Komponenten, aber Sie brauchen ein nützliches Framework. Es ist schwer zu verwenden, wie es ist, aber extrem leistungsfähig, wenn Sie wissen, wie man es benutzt. Nicht wenige Werkzeuge verwenden SPIN unter der Haube.

MultithreadedTC ist wahrscheinlich der Mainstream, aber einige der oben genannten statischen Analysewerkzeuge sind definitiv einen Blick wert.

Ich habe viel getan, und ja, es ist scheiße.

Einige Hinweise:

  • GroboUtils zum Ausführen mehrerer Test-Threads
  • alphaWorks ConTest für Geräteklassen , um Interleavings zwischen Iterationen zu variieren
  • Erstelle ein throwable und überprüfe es in tearDown (siehe Listing 1). Wenn Sie eine schlechte Ausnahme in einem anderen Thread finden, weisen Sie sie einfach throwable zu.
  • Ich habe die utils-class in Listing 2 erstellt und sie als unschätzbar empfunden, insbesondere als waitForVerify und waitForCondition, was die performance Ihrer Tests erheblich boost wird.
  • Nutzen Sie AtomicBoolean in Ihren Tests. Es ist Thread-sicher, und Sie werden oft einen letzten Referenztyp benötigen, um Werte aus Callback-classn und ähnlichem zu speichern. Siehe Beispiel in Listing 3.
  • @Test(timeout=60*1000) Sie sicher, dass Sie Ihrem Test immer einen Timeout geben (zB @Test(timeout=60*1000) ), da Concurrency Tests manchmal für immer hängen bleiben können, wenn sie kaputt sind

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, String)} with * default max wait and check period values. */ static public void waitForCondition(Predicate predicate, String 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, String errorMessage) throws Throwable { waitForCondition(maxWait_ms, checkPeriod_ms, predicate, new Closure() { public void execute(Object errorMessage) { fail((String)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 String getTestDirPathForTestClass(Object object) { String 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()); } 

Verfügbarkeit kann auch hilfreich sein, um deterministische Komponententests zu schreiben. Es erlaubt Ihnen zu warten, bis ein Zustand irgendwo in Ihrem System aktualisiert wird. Beispielsweise:

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

oder

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

Es hat auch Scala und Groovy Unterstützung.

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

Das Testen von MT-Code auf Korrektheit ist, wie bereits erwähnt, ein ziemlich hartes Problem. Am Ende läuft es darauf hinaus, sicherzustellen, dass es in Ihrem Code keine falsch synchronisierten Datenrennen gibt. Das Problem dabei ist, dass es unendlich viele Möglichkeiten der Thread-Ausführung (Interleavings) gibt, über die Sie nicht viel Kontrolle haben (lesen Sie diesen Artikel allerdings). In einfachen Szenarien könnte es möglich sein, die Richtigkeit durch Argumentation zu beweisen, aber das ist normalerweise nicht der Fall. Vor allem, wenn Sie die Synchronisierung vermeiden / minimieren und nicht die offensichtlichste / einfachste Synchronisationsoption wählen möchten.

Ein Ansatz, dem ich folge, besteht darin, hochgradig konkurrierenden Testcode zu schreiben, um potentiell unentdeckte Datenrassen wahrscheinlicher zu machen. Und dann mache ich diese Tests für einige Zeit 🙂 Ich stolperte einmal über einen Vortrag, wo einige Computerwissenschaftler ein Werkzeug vorstellten, das dies tut (zufällig Test aus Spezifikationen ersehend und dann wild herumlaufend, gleichzeitig nach den definierten Invarianten suchend) gebrochen sein).

Übrigens denke ich, dass dieser Aspekt beim Testen von MT-Code hier nicht erwähnt wurde: Identifizieren Sie Invarianten des Codes, nach denen Sie zufällig suchen können. Leider ist das Finden dieser Invarianten auch ein ziemlich schweres Problem. Außerdem werden sie möglicherweise nicht während der Ausführung gehalten, also müssen Sie Ausführungspunkte finden / erzwingen, wo Sie erwarten können, dass sie wahr sind. Es ist ebenfalls ein schwieriges Problem, die Codeausführung in einen solchen Zustand zu bringen (und könnte auch Parallelitätsprobleme verursachen). Es ist verdammt schwer!

Einige interessante Links zum Lesen:

  • Deterministisches Interleaving : Ein Framework, das es ermöglicht, bestimmte Thread-Interleavings zu erzwingen und dann nach Invarianten zu suchen
  • jMock flasher : Stress-Test-Synchronisation
  • assertConcurrent : JUnit-Version der Stresstest-Synronisierung
  • Testing Concurrent Code : Kurzer Überblick über die zwei Hauptmethoden der Brute Force (Stresstest) oder deterministisch (für die Invarianten)

Eine andere Möglichkeit, Threading-Code und (sehr) komplexe Systeme im Allgemeinen zu testen, ist Fuzz Testing . Es ist nicht großartig, und es wird nicht alles finden, aber es ist wahrscheinlich nützlich und es ist einfach zu tun.

Zitat:

Fuzz Testing oder Fuzzing ist ein Softwaretestverfahren, das zufällige Daten (“Fuzz”) für die Eingaben eines Programms bereitstellt. Wenn das Programm fehlschlägt (z. B. durch Absturz oder durch Fehler bei integrierten Code-Assertions), können die Fehler notiert werden. Der große Vorteil von Fuzz-Tests besteht darin, dass das Testdesign äußerst einfach und frei von Vorurteilen bezüglich des Systemverhaltens ist.

Fuzz-Tests werden häufig in großen Software-Entwicklungsprojekten verwendet, bei denen Black-Box-Tests zum Einsatz kommen. Diese Projekte haben normalerweise ein Budget, um Testwerkzeuge zu entwickeln, und Fuzz-Tests sind eine der Techniken, die ein hohes Kosten-Nutzen-Verhältnis bietet.

Fuzz-Tests sind jedoch kein Ersatz für erschöpfende Tests oder formale Methoden: Sie können nur eine zufällige Stichprobe des Systemverhaltens liefern, und in vielen Fällen kann ein Fuzz-Test nur zeigen, dass ein Teil der Software Ausnahmen behandelt, ohne abzustürzen, statt verhält sich richtig. Daher können Fuzz-Tests nur als ein Werkzeug zur Fehlersuche und nicht als Qualitätssicherung angesehen werden.

Pete Goodliffe hat eine Reihe von Unit-Tests von Gewinde- Code.

Es ist schwer. Ich nehme den leichteren Weg und versuche, den Threading-Code vom eigentlichen Test abzugrenzen. Pete erwähnt, dass die Art, wie ich das tue, falsch ist, aber ich habe entweder die Trennung richtig gemacht oder ich hatte nur Glück.

Für Java, lesen Sie Kapitel 12 von JCIP . Es gibt einige konkrete Beispiele für das Schreiben von deterministischen Multithread-Unit-Tests, um zumindest die Korrektheit und Invarianten von konkurrierendem Code zu testen.

Das “Prüfen” der Fadensicherheit mit Komponententests ist viel dichter. Ich glaube, dass dies durch automatisierte Integrationstests auf einer Vielzahl von Plattformen / Konfigurationen besser unterstützt wird.

Ich handle Komponententests von Gewindekomponenten auf die gleiche Weise wie ich jeden Komponententest durchführe, das heißt mit der Inversion von Steuer- und Isolationsgerüsten. Ich entwickle in der .Net-arena und out of the box ist das Threading (unter anderem) sehr hart (ich würde sagen fast unmöglich) um es vollständig zu isolieren.

Deshalb habe ich Wrapper geschrieben, die in etwa so aussehen (vereinfacht):

 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) } } 

Von dort aus kann ich den IThreadingManager leicht in meine Komponenten einfügen und mein isoliertes Framework verwenden, damit sich der Thread während des Tests wie erwartet verhält.

Das hat bisher gut für mich funktioniert, und ich verwende den gleichen Ansatz für den Thread-Pool, Dinge in System.Environment, Sleep usw. usw.

Ich schreibe gerne zwei oder mehr Testmethoden, die auf parallelen Threads ausgeführt werden, und jeder von ihnen Aufrufe in das zu testende Objekt. Ich habe Sleep () Aufrufe verwendet, um die Reihenfolge der Aufrufe von den verschiedenen Threads zu koordinieren, aber das ist nicht wirklich zuverlässig. Es ist auch viel langsamer, weil Sie lange genug schlafen müssen, damit das Timing funktioniert.

Ich fand die Multithreaded TC Java Bibliothek von der gleichen Gruppe, die FindBugs schrieb. Sie können die Reihenfolge der Ereignisse ohne Sleep () festlegen und es ist zuverlässig. Ich habe es noch nicht ausprobiert.

Die größte Einschränkung dieses Ansatzes besteht darin, dass Sie nur die Szenarien testen können, von denen Sie vermuten, dass sie Probleme verursachen. Wie andere bereits gesagt haben, müssen Sie Ihren Multithreading-Code wirklich in eine kleine Anzahl einfacher classn einteilen, um die Möglichkeit zu haben, sie gründlich zu testen.

Nachdem Sie die Szenarios sorgfältig getestet haben, von denen Sie erwarten, dass sie Probleme verursachen, ist ein unwissenschaftlicher Test, der eine Reihe von gleichzeitigen Anforderungen in der class für eine Weile austriggers, eine gute Möglichkeit, nach unerwarteten Problemen zu suchen.

Update: Ich habe ein bisschen mit der Multithreaded TC Java-Bibliothek gespielt, und es funktioniert gut. Ich habe auch einige seiner functionen auf eine .NET-Version portiert, die ich TickingTest nenne .

Sehen Sie sich meine diesbezügliche Antwort an

Entcasting einer Testklasse für eine benutzerdefinierte Barriere

Es ist voreingenommen gegenüber Java, hat aber eine vernünftige Zusammenfassung der Optionen.

Zusammenfassend (IMO) ist es nicht die Verwendung einiger schicker Frameworks, die die Korrektheit sicherstellen, sondern wie Sie Ihren Multithread-Code entcasting. Die Aufteilung der Bedenken (Nebenläufigkeit und functionalität) trägt wesentlich zur Steigerung des Vertrauens bei. Wachsende objektorientierte Software, die durch Tests geleitet wird, erklärt einige Optionen besser als ich kann.

Statische Analyse und formale Methoden (siehe Parallelität: Zustandsmodelle und Java-Programme ) sind eine Option, aber ich habe festgestellt, dass sie für die kommerzielle Entwicklung von begrenztem Nutzen sind.

Vergessen Sie nicht, dass Tests zum Laden / Eintauchen selten Probleme hervorheben.

Viel Glück!

Ich habe kürzlich (für Java) ein Tool namens Threadsafe entdeckt. Es ist ein statisches Analysewerkzeug, ähnlich wie Findbugs, aber speziell um Multi-Threading-Probleme zu erkennen. Es ist kein Ersatz für Tests, aber ich kann es als Teil des Schreibens von zuverlässigem Multithreading-Java empfehlen.

Es fängt sogar einige sehr subtile potentielle Probleme ein, wie etwa die classnsubsumtion, den Zugriff auf unsichere Objekte durch konkurrierende classn und das Auffinden fehlender flüchtiger Modifikatoren bei Verwendung des doppelt überprüften Sperrparadigmas.

Wenn Sie Multithread Java schreiben, versuchen Sie es.

Der folgende Artikel schlägt zwei Lösungen vor. Wrapping eines Semaphors (CountDownLatch) und fügt functionen wie Externalize Daten aus dem internen Thread. Eine andere Möglichkeit, diesen Zweck zu erreichen, ist die Verwendung des Thread-Pools (siehe Points of Interest).

Sprinkler – Erweitertes Synchronisationsobjekt

Ich habe den größten Teil der letzten Woche in einer Universitätsbibliothek verbracht, um Debugging von gleichzeitigem Code zu studieren. Das zentrale Problem ist, dass der gleichzeitige Code nicht deterministisch ist. Üblicherweise ist akademisches Debugging in eines von drei Camps gefallen:

  1. Ereignisverfolgung / -wiedergabe Dies erfordert einen Ereignismonitor und das Überprüfen der Ereignisse, die gesendet wurden. In einem UT-Framework würde dies bedeuten, dass die Ereignisse manuell als Teil eines Tests gesendet werden und dann Post-Mortem-Überprüfungen durchgeführt werden.
  2. Skriptfähig Hier interagieren Sie mit dem ausgeführten Code mit einer Reihe von Triggern. “Auf x> foo, baz ()”. Dies könnte in einem UT-Framework interpretiert werden, in dem Sie ein Laufzeitsystem haben, das einen bestimmten Test unter bestimmten Bedingungen austriggers.
  3. Interaktiv. Dies wird natürlich in einer automatischen Testsituation nicht funktionieren. 😉

Nun, wie die obigen Kommentatoren bemerkt haben, können Sie Ihr konkurrierendes System in einen deterministischeren Zustand bringen. Wenn Sie das jedoch nicht richtig machen, bauen Sie einfach wieder ein sequenzielles System auf.

Mein Vorschlag wäre, sich darauf zu konzentrieren, ein sehr strenges Design-Protokoll darüber zu haben, was eingefädelt wird und was nicht gefädelt wird. Wenn Sie Ihre Schnittstelle so beschränken, dass zwischen den Elementen minimale Abhängigkeiten bestehen, ist das viel einfacher.

Viel Glück und weiter an dem Problem arbeiten.

Ich hatte die unglückliche Aufgabe, Thread-Code zu testen, und sie sind definitiv die härtesten Tests, die ich je geschrieben habe.

Beim Schreiben meiner Tests habe ich eine Kombination aus Teilnehmern und Ereignissen verwendet. Im Grunde geht es darum, PropertyNotifyChanged Ereignisse mit einem WaitCallback oder irgendeiner Art von ConditionalWaiter , die WaitCallback .

Ich bin mir nicht sicher, ob dies der beste Ansatz war, aber es hat für mich geklappt.

Für J2E-Code habe ich SilkPerformer, LoadRunner und JMeter für den Parallelitätstest von Threads verwendet. Sie alle machen das Gleiche. Im Grunde geben sie Ihnen eine relativ einfache Schnittstelle für die Verwaltung ihrer Version des Proxy-Servers, die erforderlich ist, um den TCP / IP-Datenstrom zu analysieren und mehrere Benutzer zu simulieren, die gleichzeitig Anfragen an Ihren Anwendungsserver stellen. Der Proxy-Server kann Ihnen die Möglichkeit geben, die Anforderungen zu analysieren, indem Sie die gesamte Seite und URL, die an den Server gesendet wurden, sowie die Antwort vom Server nach der Verarbeitung der Anfrage präsentieren.

Sie können einige Fehler im unsicheren http-Modus finden, wo Sie zumindest die gesendeten Formulardaten analysieren und diese für jeden Benutzer systematisch ändern können. Aber die wahren Tests sind, wenn Sie in https (Secured Socket Layers) laufen. Dann müssen Sie auch damit zu kämpfen haben, die Session- und Cookie-Daten, die ein wenig mehr verschachtelt sein können, systematisch zu verändern.

Der beste Fehler, den ich jemals beim Testen der Parallelität fand, war, als ich entdeckte, dass der Entwickler sich bei der Anmeldung auf die Java-Speicherbereinigung verlassen hatte, um die Verbindungsanforderung beim LDAP-Server zu schließen. Dies führte dazu, dass Benutzer entlarvt wurden zu den Sitzungen anderer Benutzer und sehr verwirrenden Ergebnissen, wenn man versucht zu analysieren, was passiert ist, als der Server in die Knie ging, kaum in der Lage, eine Transaktion alle paar Sekunden abzuschließen.

Am Ende werden Sie oder jemand wahrscheinlich den Code für Fehler wie den, den ich gerade erwähnt habe, analysieren und analysieren. Und eine offene Diskussion zwischen den Abteilungen, wie die, die stattgefunden hat, als wir das oben beschriebene Problem entfalteten, sind am nützlichsten. Aber diese Tools sind die beste Lösung zum Testen von Multi-Threaded-Code. JMeter ist Open Source. SilkPerformer und LoadRunner sind proprietär. Wenn Sie wirklich wissen wollen, ob Ihre App threadsicher ist, tun das die großen Jungs. Ich habe das für sehr große Firmen professionell gemacht, also rate ich nicht. Ich spreche aus eigener Erfahrung.

Ein Wort der Vorsicht: Es braucht einige Zeit, um diese Werkzeuge zu verstehen. Es wird nicht einfach sein, die Software zu installieren und die GUI zu starten, es sei denn, Sie haben schon einige Erfahrungen mit Multi-Thread-Programmierung gemacht. Ich habe versucht, die 3 kritischen Kategorien der zu verstehenden Bereiche (Formulare, Session- und Cookie-Daten) zu identifizieren, in der Hoffnung, dass zumindest das Verständnis dieser Themen Ihnen helfen wird, sich auf schnelle Ergebnisse zu konzentrieren, im Gegensatz zu den gesamte Dokumentation.

Parallelität ist ein komplexes Zusammenspiel zwischen Speichermodell, Hardware, Caches und unserem Code. Im Fall von Java wurden zumindest solche Tests hauptsächlich von jcstress angesprochen. Die Ersteller dieser Bibliothek sind bekanntermaßen Autoren vieler JVM-, GC- und Java-concurrencysmerkmale.

Aber selbst diese Bibliothek benötigt gute Kenntnisse der Java Memory Model-Spezifikation, damit wir genau wissen, was wir testen. Aber ich denke, der Fokus dieser Bemühungen ist mircobenchmarks. Keine riesigen Geschäftsanwendungen.

Wenn Sie einen einfachen neuen Thread testen (runnable) .run () Dann können Sie Thread vortäuschen, um den Runnable sequenziell auszuführen

Zum Beispiel, wenn der Code des getesteten Objekts einen neuen Thread wie diesen aufruft

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

Dann können neue Threads verspottet und das runnable Argument sequentiell ausgeführt werden

 @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 } 

(wenn möglich) benutze keine Threads, benutze Schauspieler / aktive Objekte. Einfach zu testen.

Sie können EasyMock.makeThreadSafe verwenden, um das Testen der Instanz threadsicher zu machen