Ich habe bisher den Albtraum vermieden, der Multithread-Code testet, da er einfach wie ein zu großes Minenfeld erscheint. Ich möchte fragen, wie Leute Code getestet haben, der für eine erfolgreiche Ausführung auf Threads basiert, oder wie Leute solche Probleme getestet haben, die nur auftreten, wenn zwei Threads auf eine bestimmte Weise interagieren.
Dies scheint heute ein wirklich zentrales Problem für Programmierer zu sein. Es wäre nützlich, unser Wissen über dieses Problem zu bündeln.
Antworten:
Es gibt keinen einfachen Weg, dies zu tun. Ich arbeite an einem Projekt, das von Natur aus Multithreading ist. Ereignisse kommen vom Betriebssystem und ich muss sie gleichzeitig verarbeiten.
Der einfachste Weg, um komplexen Multithread-Anwendungscode zu testen, ist folgender: Wenn er zu komplex zum Testen ist, machen Sie es falsch. Wenn Sie eine einzelne Instanz haben, auf die mehrere Threads einwirken, und Sie keine Situationen testen können, in denen diese Threads sich gegenseitig überschneiden, muss Ihr Entwurf überarbeitet werden. Es ist so einfach und so komplex.
Es gibt viele Möglichkeiten, Multithreading zu programmieren, um zu vermeiden, dass Threads gleichzeitig durch Instanzen laufen. Am einfachsten ist es, alle Ihre 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 derselben Instanz interagieren, und die Anzahl dieser Stellen verringern. Auf diese Weise isolieren Sie einige Klassen, in denen tatsächlich Multithreading auftritt, und reduzieren so die Gesamtkomplexität beim Testen Ihres Systems.
Aber Sie müssen erkennen, dass Sie selbst dann nicht jede Situation testen können, in der zwei Threads aufeinander treten. Dazu müssten Sie zwei Threads gleichzeitig im selben Test ausführen und dann genau steuern, welche Zeilen sie zu einem bestimmten Zeitpunkt ausführen. Das Beste, was Sie tun können, ist, diese Situation zu simulieren. Möglicherweise müssen Sie jedoch speziell zum Testen codieren. Dies ist bestenfalls ein halber Schritt in Richtung einer echten Lösung.
Der wahrscheinlich beste Weg, um Code auf Threading-Probleme zu testen, ist die statische Analyse des Codes. Wenn Ihr Thread-Code nicht einem endlichen Satz von thread-sicheren Mustern folgt, liegt möglicherweise ein Problem vor. Ich glaube, dass die Codeanalyse in VS einige Kenntnisse über das Threading enthält, aber wahrscheinlich nicht viel.
Schauen Sie, wie die Dinge derzeit stehen (und wahrscheinlich für eine gute Zeit stehen werden), ist der beste Weg, Multithread-Apps zu testen, die Komplexität von Thread-Code so weit wie möglich zu reduzieren. Minimieren Sie Bereiche, in denen Threads interagieren, testen Sie sie so gut wie möglich und verwenden Sie die Codeanalyse, um Gefahrenbereiche zu identifizieren.
quelle
Es ist schon eine Weile her, dass diese Frage gestellt wurde, aber sie wird immer noch nicht beantwortet ...
Die Antwort von kleolb02 ist gut. Ich werde versuchen, auf weitere Details einzugehen.
Es gibt einen Weg, den ich für C # -Code übe. Für Komponententests sollten Sie reproduzierbare Tests programmieren können, was die größte Herausforderung bei Multithread-Code darstellt. Meine Antwort zielt also darauf ab, asynchronen Code in ein Testkabel zu zwingen, das synchron funktioniert .
Es ist eine Idee aus Gerard Meszardos 'Buch " xUnit Test Patterns " und heißt "Humble Object" (S. 695): Sie müssen den Kernlogikcode und alles, was nach asynchronem Code riecht, voneinander trennen. Dies würde zu einer Klasse für die Kernlogik führen, die synchron arbeitet .
Dies versetzt Sie in die Lage, den Kernlogikcode synchron zu testen . Sie haben die absolute Kontrolle über das Timing der Anrufe, die Sie in der Kernlogik ausführen, und können so reproduzierbare Tests durchführen. Und dies ist Ihr Gewinn aus der Trennung von Kernlogik und asynchroner Logik.
Diese Kernlogik muss von einer anderen Klasse umschlossen werden, die für den asynchronen Empfang von Aufrufen an die Kernlogik verantwortlich ist und diese Aufrufe an die Kernlogik delegiert . Produktionscode greift nur über diese Klasse auf die Kernlogik zu. Da diese Klasse nur Aufrufe delegieren sollte, ist sie eine sehr "dumme" Klasse ohne viel Logik. So können Sie Ihre Komponententests für diese asychrone Arbeiterklasse auf ein Minimum beschränken.
Alles darüber hinaus (Testen der Interaktion zwischen Klassen) sind Komponententests. Auch in diesem Fall sollten Sie die absolute Kontrolle über das Timing haben können, wenn Sie sich an das Muster "Bescheidenes Objekt" halten.
quelle
Tatsächlich eine harte! In meinen (C ++) Unit-Tests habe ich dies nach dem verwendeten Parallelitätsmuster in mehrere Kategorien unterteilt:
Unit-Tests für Klassen, die in einem einzelnen Thread ausgeführt werden und nicht threadbewusst sind - einfach, wie gewohnt testen.
Komponententests für Monitorobjekte (solche, die synchronisierte Methoden im Kontrollthread des Aufrufers ausführen), die eine synchronisierte öffentliche API verfügbar machen - instanziieren Sie mehrere Schein-Threads, die die API ausführen. Konstruieren Sie Szenarien, die interne Bedingungen des passiven Objekts ausüben. Schließen Sie einen längeren Test ein, der im Grunde genommen von mehreren Threads für einen langen Zeitraum übertroffen wird. Ich weiß, dass dies unwissenschaftlich ist, aber es schafft Vertrauen.
Unit-Tests für aktive Objekte (solche, die ihren eigenen Thread oder ihre eigenen Kontroll-Threads kapseln) - ähnlich wie oben Nr. 2 mit Abweichungen je nach Klassendesign. Die öffentliche API kann blockieren oder nicht blockieren, Anrufer können Futures erhalten, Daten können in Warteschlangen eintreffen oder müssen aus der Warteschlange entfernt werden. Hier sind viele Kombinationen möglich; weiße Box weg. Erfordert weiterhin mehrere Mock-Threads, um das zu testende Objekt aufzurufen.
Nebenbei:
In meiner internen Entwicklerschulung unterrichte ich die Säulen der Parallelität und diese beiden Muster als primären Rahmen für das Nachdenken und Zerlegen von Parallelitätsproblemen. Es gibt offensichtlich fortgeschrittenere Konzepte, aber ich habe festgestellt, dass diese Grundlagen dazu beitragen, Ingenieure von der Suppe fernzuhalten. Dies führt auch zu Code, der wie oben beschrieben besser auf Einheiten getestet werden kann.
quelle
Ich bin in den letzten Jahren mehrmals auf dieses Problem gestoßen, als ich Code für die Thread-Behandlung für mehrere Projekte geschrieben habe. Ich gebe eine späte Antwort, da die meisten anderen Antworten, obwohl sie Alternativen bieten, die Frage zum Testen nicht wirklich beantworten. Meine Antwort richtet sich an Fälle, in denen es keine Alternative zu Multithread-Code gibt. Der Vollständigkeit halber gehe ich auf Fragen des Code-Designs ein, diskutiere aber auch Unit-Tests.
Schreiben von testbarem Multithread-Code
Das erste, was Sie tun müssen, ist, Ihren Produktions-Thread-Bearbeitungscode von dem gesamten Code zu trennen, der die eigentliche Datenverarbeitung ausführt. Auf diese Weise kann die Datenverarbeitung als Code mit einfachem Thread getestet werden, und der Multithread-Code kann nur Threads koordinieren.
Das zweite, woran Sie sich erinnern sollten, ist, dass Fehler in Multithread-Code wahrscheinlich sind. Die Fehler, die sich am seltensten manifestieren, sind die Fehler, die sich in die Produktion einschleichen, selbst in der Produktion schwer zu reproduzieren sind und daher die größten Probleme verursachen. Aus diesem Grund ist der Standard-Codierungsansatz, den Code schnell zu schreiben und dann zu debuggen, bis er funktioniert, eine schlechte Idee für Multithread-Code. Dies führt zu Code, in dem die einfachen Fehler behoben sind und die gefährlichen Fehler immer noch vorhanden sind.
Stattdessen müssen Sie beim Schreiben von Multithread-Code den Code mit der Einstellung schreiben, dass Sie das Schreiben der Fehler überhaupt vermeiden möchten. Wenn Sie den Datenverarbeitungscode ordnungsgemäß entfernt haben, sollte der Thread-Verarbeitungscode so klein sein - vorzugsweise einige Zeilen, im schlimmsten Fall einige 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 Einfädeln verstehen, nehmen Sie sich Zeit und seien Sie vorsichtig.
Schreiben von Komponententests für Multithread-Code
Sobald der Multithread-Code so sorgfältig wie möglich geschrieben wurde, lohnt es sich immer noch, Tests für diesen Code zu schreiben. Der Hauptzweck der Tests besteht nicht darin, auf stark zeitabhängige Race-Condition-Fehler zu testen - es ist unmöglich, diese Race-Bedingungen wiederholt zu testen -, sondern vielmehr darauf, zu testen, ob Ihre Sperrstrategie zur Verhinderung solcher Fehler die Interaktion mehrerer Threads wie beabsichtigt ermöglicht .
Um das korrekte Sperrverhalten ordnungsgemäß zu testen, muss ein Test mehrere Threads starten. Um den Test wiederholbar zu machen, möchten wir, dass die Interaktionen zwischen den Threads in einer vorhersehbaren Reihenfolge stattfinden. Wir möchten die Threads im Test nicht extern synchronisieren, da dadurch Fehler maskiert werden, die in der Produktion auftreten können, wenn die Threads nicht extern synchronisiert sind. Dadurch bleiben Zeitverzögerungen für die Thread-Synchronisation übrig. Dies ist die Technik, die ich erfolgreich angewendet habe, wenn ich Tests mit 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 deaktiviert ist und der Test fehlschlägt. Was ich normalerweise getan habe, ist, mit Verzögerungen zu beginnen, die zu Testfehlern führen, die Verzögerungen zu erhöhen, damit der Test zuverlässig auf meinem Entwicklungscomputer bestanden wird, und dann die Verzögerungen darüber hinaus zu verdoppeln, damit der Test eine gute Chance hat, andere Computer zu bestehen. Dies bedeutet, dass der Test eine makroskopische Zeit in Anspruch nimmt, obwohl nach meiner Erfahrung ein sorgfältiges Testdesign diese Zeit auf nicht mehr als ein Dutzend Sekunden beschränken kann. Da Ihre Anwendung nicht sehr viele Stellen enthalten sollte, an denen Thread-Koordinierungscode erforderlich ist, sollte dies für Ihre Testsuite akzeptabel sein.
Verfolgen Sie abschließend die Anzahl der Fehler, die von Ihrem Test erfasst wurden. Wenn Ihr Test eine Codeabdeckung von 80% aufweist, kann davon ausgegangen werden, dass etwa 80% Ihrer Fehler erkannt werden. Wenn Ihr Test gut konzipiert ist, aber keine Fehler findet, besteht eine vernünftige Wahrscheinlichkeit, dass Sie keine zusätzlichen Fehler haben, die nur in der Produktion auftreten. Wenn der Test ein oder zwei Fehler entdeckt, haben Sie möglicherweise immer noch Glück. Darüber hinaus möchten Sie möglicherweise eine sorgfältige Überprüfung oder sogar eine vollständige Neufassung Ihres Thread-Handhabungscodes in Betracht ziehen, da der Code wahrscheinlich immer noch versteckte Fehler enthält, die nur sehr schwer zu finden sind, bis der Code in Produktion ist, und zwar sehr dann schwer zu reparieren.
quelle
Ich hatte auch ernsthafte Probleme beim Testen von Multithread-Code. Dann habe ich in "xUnit Test Patterns" von Gerard Meszaros eine wirklich coole Lösung gefunden. Das Muster, das er beschreibt, heißt Humble Object .
Grundsätzlich wird beschrieben, wie Sie die Logik in eine separate, einfach 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 (Multithreading, asynchrone Ausführung usw.)
quelle
Es gibt ein paar Tools, die ziemlich gut sind. Hier ist eine Zusammenfassung einiger Java-Versionen.
Einige gute statische Analysewerkzeuge umfassen FindBugs (gibt einige nützliche Hinweise), JLint , Java Pathfinder (JPF & JPF2) und Bogor .
MultithreadedTC ist ein ziemlich gutes dynamisches Analysetool (in JUnit integriert), mit dem Sie Ihre eigenen Testfälle einrichten müssen.
Der ConTest von IBM Research ist interessant. Es instrumentiert Ihren Code, indem es alle Arten von Verhaltensweisen zum Ändern von Threads (z. B. Schlaf & Ertrag) einfügt, um zu versuchen, Fehler zufällig aufzudecken.
SPIN ist ein wirklich cooles Tool zum Modellieren Ihrer Java (und anderer) Komponenten, aber Sie benötigen ein nützliches Framework. Es ist schwer zu bedienen, 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 aufgeführten statischen Analysewerkzeuge sind auf jeden Fall einen Blick wert.
quelle
Die Wartbarkeit kann auch hilfreich sein, um deterministische Komponententests zu schreiben. Sie können warten, bis ein Status irgendwo in Ihrem System aktualisiert wird. Zum Beispiel:
oder
Es hat auch Scala und Groovy Unterstützung.
quelle
Eine andere Möglichkeit, Thread-Code und sehr komplexe Systeme im Allgemeinen (irgendwie) zu testen, ist das Fuzz-Testen . Es ist nicht großartig und es wird nicht alles finden, aber es ist wahrscheinlich nützlich und einfach zu tun.
Zitat:
quelle
Ich habe viel getan, und ja, es ist scheiße.
Einige Hinweise:
throwable
Feld und checken Sie es eintearDown
(siehe Listing 1). Wenn Sie eine schlechte Ausnahme in einem anderen Thread abfangen, weisen Sie sie einfach throwable zu.AtomicBoolean
in Ihren Tests. Es ist threadsicher und Sie benötigen häufig einen endgültigen Referenztyp, um Werte aus Rückrufklassen und dergleichen zu speichern. Siehe Beispiel in Listing 3.@Test(timeout=60*1000)
), da Parallelitätstests manchmal für immer hängen bleiben, wenn sie fehlerhaft sind.Listing 1:
Listing 2:
Listing 3:
quelle
Das Testen des MT-Codes auf Richtigkeit ist, wie bereits erwähnt, ein ziemlich schwieriges Problem. Am Ende geht es darum, sicherzustellen, dass Ihr Code keine falsch synchronisierten Datenrennen enthält. 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 jedoch unbedingt diesen Artikel). In einfachen Szenarien ist es möglicherweise möglich, die Richtigkeit durch Argumentation tatsächlich zu beweisen, dies ist jedoch normalerweise nicht der Fall. Vor allem, wenn Sie die Synchronisation vermeiden / minimieren und nicht die offensichtlichste / einfachste Synchronisationsoption wählen möchten.
Ein Ansatz, dem ich folge, besteht darin, hochgradig gleichzeitigen Testcode zu schreiben, damit potenziell unentdeckte Datenrennen wahrscheinlich auftreten. Und dann führe ich diese Tests für einige Zeit durch :) Ich bin einmal auf einen Vortrag gestoßen, in dem ein Informatiker ein Tool vorführte, das dies tut (zufällig einen Test aus Spezifikationen erstellt und sie dann wild und gleichzeitig ausführt, um nach den definierten Invarianten zu suchen gebrochen sein).
Übrigens, ich denke, dieser Aspekt des Testens von MT-Code wurde hier nicht erwähnt: Identifizieren Sie Invarianten des Codes, auf die Sie zufällig prüfen können. Leider ist es auch ein ziemlich schwieriges Problem, diese Invarianten zu finden. Außerdem halten sie möglicherweise nicht die ganze Zeit während der Ausführung, sodass Sie Ausführungspunkte finden / erzwingen müssen, an denen Sie erwarten können, dass sie wahr sind. Die Codeausführung in einen solchen Zustand zu bringen, ist ebenfalls ein schwieriges Problem (und kann selbst zu Parallelitätsproblemen führen. Puh, es ist verdammt schwierig!
Einige interessante Links zum Lesen:
quelle
Pete Goodliffe hat eine Serie auf der Unit-Testen von Thread- Code.
Es ist schwer. Ich gehe den einfacheren Ausweg und versuche, den Threading-Code vom eigentlichen Test zu abstrahieren. Pete erwähnt zwar, dass die Art und Weise, wie ich es mache, falsch ist, aber ich habe entweder die richtige Trennung oder ich hatte einfach Glück.
quelle
Informationen zu Java finden Sie in Kapitel 12 von Informationen zu JCIP . Es gibt einige konkrete Beispiele für das Schreiben deterministischer Unit-Tests mit mehreren Threads, um zumindest die Richtigkeit und Invarianten von gleichzeitigem Code zu testen.
Das "Beweisen" der Gewindesicherheit mit Unit-Tests ist viel schwieriger. Meiner Meinung nach wird dies durch automatisierte Integrationstests auf einer Vielzahl von Plattformen / Konfigurationen besser unterstützt.
quelle
Ich schreibe gerne zwei oder mehr Testmethoden, die auf parallelen Threads ausgeführt werden sollen, und jede von ihnen ruft das zu testende Objekt auf. Ich habe Sleep () -Aufrufe verwendet, um die Reihenfolge der Aufrufe aus den verschiedenen Threads zu koordinieren, aber das ist nicht wirklich zuverlässig. Es ist auch viel langsamer, weil man lange genug schlafen muss, damit das Timing normalerweise funktioniert.
Ich habe die Multithreaded TC Java-Bibliothek aus derselben Gruppe gefunden, die FindBugs geschrieben hat. Sie können die Reihenfolge der Ereignisse angeben, ohne Sleep () zu verwenden, und es ist zuverlässig. Ich habe es noch nicht versucht.
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 gesagt haben, müssen Sie Ihren Multithread-Code wirklich in eine kleine Anzahl einfacher Klassen isolieren, um die Hoffnung zu haben, sie gründlich zu testen.
Nachdem Sie die Szenarien, von denen Sie erwarten, dass sie Probleme verursachen, sorgfältig getestet haben, ist ein unwissenschaftlicher Test, bei dem eine Reihe von gleichzeitigen Anforderungen für eine Weile an die Klasse gesendet werden, 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 Funktionen auf eine .NET-Version portiert, die ich TickingTest nenne .
quelle
Ich behandle Unit-Tests von Gewindekomponenten genauso wie Unit-Tests, dh mit Inversion von Steuerungs- und Isolations-Frameworks. Ich entwickle in der .Net-Arena und sofort ist das Einfädeln (unter anderem) sehr schwer (ich würde sagen fast unmöglich) vollständig zu isolieren.
Deshalb habe ich Wrapper geschrieben, die ungefähr so aussehen (vereinfacht):
Von dort aus kann ich den IThreadingManager problemlos in meine Komponenten einfügen und mein bevorzugtes Isolationsframework verwenden, damit sich der Thread wie erwartet während des Tests verhält.
Das hat bisher für mich großartig funktioniert, und ich verwende den gleichen Ansatz für den Thread-Pool, die Dinge in System.Environment, Sleep usw. usw.
quelle
Schauen Sie sich meine Antwort unter an
Entwerfen einer Testklasse für eine benutzerdefinierte Barriere
Es ist voreingenommen in Richtung Java, hat aber eine vernünftige Zusammenfassung der Optionen.
Zusammenfassend (IMO) ist es jedoch nicht die Verwendung eines ausgefallenen Frameworks, das die Korrektheit gewährleistet, sondern wie Sie Ihren Multithread-Code entwerfen. Die Aufteilung der Bedenken (Parallelität und Funktionalität) trägt wesentlich zur Vertrauensbildung bei. Wachsende objektorientierte Software, die von Tests geleitet wird, erklärt einige Optionen besser als ich.
Statische Analysen und formale Methoden (siehe Parallelität: Zustandsmodelle und Java-Programme ) sind eine Option, aber ich habe festgestellt, dass sie in der kommerziellen Entwicklung nur begrenzt von Nutzen sind.
Vergessen Sie nicht, dass Last- / Einweich-Stiltests selten garantiert Probleme hervorheben.
Viel Glück!
quelle
tempus-fugit
Bibliothek erwähnen , diehelps write and test concurrent code
;)Ich habe kürzlich (für Java) ein Tool namens Threadsafe entdeckt. Es ist ein statisches Analysetool, das Findbugs ähnelt, jedoch speziell Multithreading-Probleme erkennt. Es ist kein Ersatz für Tests, aber ich kann es als Teil des Schreibens von zuverlässigem Multithread-Java empfehlen.
Es werden sogar einige sehr subtile potenzielle Probleme im Zusammenhang mit der Subsumtierung von Klassen, dem Zugriff auf unsichere Objekte über gleichzeitige Klassen und dem Erkennen fehlender flüchtiger Modifikatoren bei Verwendung des doppelt überprüften Sperrparadigmas erfasst.
Wenn Sie Multithread-Java schreiben, probieren Sie es aus.
quelle
Der folgende Artikel schlägt 2 Lösungen vor. Umschließen eines Semaphors (CountDownLatch) und Hinzufügen von Funktionen wie Externalisieren von Daten aus internen Threads. Eine andere Möglichkeit, diesen Zweck zu erreichen, ist die Verwendung des Thread-Pools (siehe Points of Interest).
Sprinkler - Erweitertes Synchronisationsobjekt
quelle
Ich habe die meiste Zeit der letzten Woche in einer Universitätsbibliothek verbracht, um das Debuggen von gleichzeitigem Code zu studieren. Das zentrale Problem ist, dass der gleichzeitige Code nicht deterministisch ist. Typischerweise ist das akademische Debuggen hier in eines von drei Lagern gefallen:
Wie die obigen Kommentatoren bemerkt haben, können Sie Ihr gleichzeitiges System jetzt in einen deterministischeren Zustand versetzen. Wenn Sie dies jedoch nicht richtig machen, können Sie wieder ein sequentielles System entwerfen.
Mein Vorschlag wäre, sich darauf zu konzentrieren, ein sehr strenges Entwurfsprotokoll darüber zu haben, was eingefädelt wird und was nicht. Wenn Sie Ihre Benutzeroberfläche so einschränken, dass nur minimale Abhängigkeiten zwischen Elementen bestehen, ist dies viel einfacher.
Viel Glück und arbeite weiter an dem Problem.
quelle
Ich hatte die unglückliche Aufgabe, Thread-Code zu testen, und es sind definitiv die schwierigsten Tests, die ich je geschrieben habe.
Beim Schreiben meiner Tests habe ich eine Kombination aus Delegierten und Ereignissen verwendet. Grundsätzlich geht es darum,
PropertyNotifyChanged
Ereignisse mit einerWaitCallback
oder einer solchenConditionalWaiter
Umfrage zu verwenden.Ich bin mir nicht sicher, ob dies der beste Ansatz war, aber es hat für mich geklappt.
quelle
Unter "Multithread" -Code anzunehmen, war etwas gemeint, das ist
Mit anderen Worten, wir sprechen über das Testen von benutzerdefinierten, statusbehafteten, threadsicheren Klassen / Methoden / Einheiten - was heutzutage ein sehr seltenes Tier sein sollte.
Da dieses Biest selten ist, müssen wir zunächst sicherstellen, dass es alle gültigen Ausreden gibt, um es zu schreiben.
Schritt 1. Erwägen Sie, den Status im selben Synchronisationskontext zu ändern.
Heutzutage ist es einfach, komponierbaren gleichzeitigen und asynchronen Code zu schreiben, bei dem E / A oder andere langsame Vorgänge in den Hintergrund verlagert werden, der gemeinsam genutzte Status jedoch in einem Synchronisationskontext aktualisiert und abgefragt wird. zB asynchrone / wartende Aufgaben und Rx in .NET usw. - sie sind alle vom Design her testbar. "echte" Aufgaben und Scheduler können ersetzt werden, um das Testen deterministisch zu machen (dies kommt jedoch nicht in Frage).
Es mag sehr eingeschränkt klingen, aber dieser Ansatz funktioniert überraschend gut. Es ist möglich, ganze Apps in diesem Stil zu schreiben, ohne dass ein Status threadsicher gemacht werden muss (das tue ich).
Schritt 2. Wenn eine Manipulation des gemeinsam genutzten Status in einem einzelnen Synchronisationskontext absolut nicht möglich ist.
Stellen Sie sicher, dass das Rad nicht neu erfunden wird / es gibt definitiv keine Standardalternative, die für den Job angepasst werden kann. Es sollte wahrscheinlich sein, dass Code sehr zusammenhängend ist und in einer Einheit enthalten ist, z. B. mit einer guten Chance, dass es sich um einen Sonderfall einer Standard-Thread-sicheren Datenstruktur wie Hash-Map oder Sammlung oder was auch immer handelt.
Hinweis: Wenn der Code groß ist / sich über mehrere Klassen erstreckt UND eine Manipulation des Multithread-Status erfordert, besteht eine sehr hohe Wahrscheinlichkeit, dass das Design nicht gut ist. Überdenken Sie Schritt 1
Schritt 3. Wenn dieser Schritt erreicht ist, müssen wir unsere eigene benutzerdefinierte Stateful Thread-sichere Klasse / Methode / Einheit testen .
Ich bin absolut ehrlich: Ich musste nie richtige Tests für solchen Code schreiben. Die meiste Zeit komme ich in Schritt 1 davon, manchmal in Schritt 2. Das letzte Mal, als ich benutzerdefinierten thread-sicheren Code schreiben musste, war so viele Jahre her, dass ich Unit-Tests eingeführt habe / wahrscheinlich musste ich ihn nicht schreiben mit dem aktuellen Wissen sowieso.
Wenn ich diesen Code wirklich testen müsste ( endlich die eigentliche Antwort ), würde ich unten einige Dinge ausprobieren
Nicht deterministische Stresstests. Führen Sie beispielsweise 100 Threads gleichzeitig aus und überprüfen Sie, ob das Endergebnis konsistent ist. Dies ist typischer für Tests auf höherer Ebene / Integration von Szenarien mit mehreren Benutzern, kann jedoch auch auf Einheitenebene verwendet werden.
Stellen Sie einige Test-Hooks bereit, in die der Test Code einfügen kann, um deterministische Szenarien zu erstellen, in denen ein Thread eine Operation vor dem anderen ausführen muss. So hässlich es auch ist, ich kann mir nichts Besseres vorstellen.
Verzögerungsgesteuertes Testen, um Threads auszuführen und Vorgänge in einer bestimmten Reihenfolge auszuführen. Genau genommen sind solche Tests auch nicht deterministisch (es besteht die Möglichkeit einer System-Freeze / Stop-the-World-GC-Sammlung, die ansonsten orchestrierte Verzögerungen verzerren kann). Außerdem ist sie hässlich, ermöglicht jedoch das Vermeiden von Hooks.
quelle
Für J2E-Code habe ich SilkPerformer, LoadRunner und JMeter zum Testen der Parallelität von Threads verwendet. Sie alle machen das Gleiche. Grundsätzlich bieten sie Ihnen eine relativ einfache Schnittstelle für die Verwaltung ihrer Version des Proxyservers, die erforderlich ist, um den TCP / IP-Datenstrom zu analysieren und mehrere Benutzer zu simulieren, die gleichzeitig Anforderungen an Ihren App-Server stellen. Mit dem Proxyserver können Sie beispielsweise die gestellten Anforderungen analysieren, indem Sie die gesamte Seite und die an den Server gesendete URL sowie die Antwort des Servers nach der Verarbeitung der Anforderung anzeigen.
Im unsicheren http-Modus finden Sie einige Fehler, bei denen Sie zumindest die gesendeten Formulardaten analysieren und diese für jeden Benutzer systematisch ändern können. Die wahren Tests sind jedoch, wenn Sie in https (Secured Socket Layers) ausgeführt werden. Dann müssen Sie auch damit kämpfen, die Sitzungs- und Cookie-Daten systematisch zu ändern, was etwas komplizierter sein kann.
Der beste Fehler, den ich jemals beim Testen der Parallelität gefunden habe, war, als ich feststellte, dass sich der Entwickler beim Anmelden auf die Java-Garbage Collection verlassen hatte, um die Verbindungsanforderung zu schließen, die beim Anmelden beim LDAP-Server hergestellt wurde. Dies führte dazu, dass Benutzer offengelegt wurden zu den Sitzungen anderer Benutzer und zu sehr verwirrenden Ergebnissen, wenn versucht wird zu analysieren, was passiert ist, als der Server in die Knie gezwungen wurde, und kaum in der Lage ist, alle paar Sekunden eine Transaktion abzuschließen.
Am Ende müssen Sie oder jemand wahrscheinlich den Code auf Fehler wie den gerade erwähnten analysieren. Und eine offene abteilungsübergreifende Diskussion, wie sie bei der Lösung des oben beschriebenen Problems stattgefunden hat, ist am nützlichsten. Diese Tools sind jedoch die beste Lösung zum Testen von Multithread-Code. JMeter ist Open Source. SilkPerformer und LoadRunner sind proprietär. Wenn Sie wirklich wissen möchten, ob Ihre App threadsicher ist, machen es die großen Jungs so. Ich habe dies für sehr große Unternehmen professionell gemacht, also rate ich nicht. Ich spreche aus persönlicher Erfahrung.
Ein Wort der Vorsicht: Es dauert einige Zeit, um diese Tools zu verstehen. Es geht nicht nur darum, die Software zu installieren und die GUI zu starten, es sei denn, Sie waren bereits mit Multithread-Programmierung vertraut. Ich habe versucht, die drei kritischen Kategorien von Bereichen zu identifizieren, die verstanden werden müssen (Formulare, Sitzungs- und Cookie-Daten), in der Hoffnung, dass Sie sich zumindest beim Verständnis dieser Themen auf schnelle Ergebnisse konzentrieren können, anstatt die zu lesen gesamte Dokumentation.
quelle
Parallelität ist ein komplexes Zusammenspiel zwischen Speichermodell, Hardware, Caches und unserem Code. Im Fall von Java wurden zumindest solche Tests teilweise hauptsächlich von jcstress behandelt . Es ist bekannt, dass die Ersteller dieser Bibliothek Autoren vieler JVM-, GC- und Java-Parallelitätsfunktionen sind.
Aber auch diese Bibliothek benötigt gute Kenntnisse der Java Memory Model-Spezifikation, damit wir genau wissen, was wir testen. Aber ich denke, der Schwerpunkt dieser Bemühungen liegt auf mircobenchmarks. Keine großen Geschäftsanwendungen.
quelle
Es gibt einen Artikel zu diesem Thema, in dem Rust als Sprache im Beispielcode verwendet wird:
https://medium.com/@polyglot_factotum/rust-concurrency-five-easy-pieces-871f1c62906a
Zusammenfassend besteht der Trick darin, Ihre gleichzeitige Logik so zu schreiben, dass sie gegenüber dem Nichtdeterminismus, der mit mehreren Ausführungsthreads verbunden ist, robust ist, indem Tools wie Kanäle und Kondvare verwendet werden.
Wenn Sie Ihre "Komponenten" auf diese Weise strukturiert haben, können Sie sie am einfachsten testen, indem Sie Kanäle zum Senden von Nachrichten an sie verwenden und dann andere Kanäle blockieren, um zu bestätigen, dass die Komponente bestimmte erwartete Nachrichten sendet.
Der verlinkte Artikel wird vollständig mit Unit-Tests geschrieben.
quelle
Wenn Sie einen einfachen neuen Thread testen (ausführbar) .run () Sie können Thread verspotten, um die ausführbare Datei nacheinander auszuführen
Zum Beispiel, wenn der Code des getesteten Objekts einen neuen Thread wie diesen aufruft
Dann kann es hilfreich sein, neue Threads zu verspotten und das ausführbare Argument nacheinander auszuführen
quelle
(wenn möglich) Verwenden Sie keine Threads, sondern Akteure / aktive Objekte. Einfach zu testen.
quelle
Sie können EasyMock.makeThreadSafe verwenden, um die Testinstanz threadsicher zu machen
quelle