Ich schreibe eine Klasse, von der ich weiß, dass sie eine Sperre benötigt, weil die Klasse Thread-sicher sein muss. Aber da ich Test-Driven-Developing bin, weiß ich, dass ich vor dem Erstellen eines Tests keine Codezeile schreiben kann. Und das finde ich sehr schwierig, da die Tests am Ende sehr komplex werden. Was machst du normalerweise in diesen Fällen? Gibt es ein Tool, um damit zu helfen?
Diese Frage ist .NET spezifisch
Jemand hat nach dem Code gefragt:
%Vor%Wenn Sie Multi-Threaded-Code schreiben, müssen Sie Ihr Gehirn noch mehr nutzen als gewöhnlich. Sie müssen logisch über jede einzelne Codezeile nachdenken, unabhängig davon, ob sie threadsicher ist oder nicht. Es ist, als würde man die Richtigkeit einer mathematischen Formel beweisen - man kann Dinge wie "N + 1 & gt; N für alle N" nicht beweisen, indem man nur Beispiele für Werte von N gibt, mit denen die Formel wahr ist. Ebenso ist es nicht möglich, zu beweisen, dass eine Klasse threadsicher ist, indem Testfälle geschrieben werden, die versuchen, Probleme damit aufzudecken. Mit einem Test ist es nur möglich zu beweisen, dass ein Fehler vorliegt, aber nicht dass es keine Fehler gibt.
Das Beste, was Sie tun können, ist, den Bedarf an Multithread-Code zu minimieren. Vorzugsweise sollte die Anwendung keinen Multi-Threaded-Code haben (zum Beispiel indem sie sich auf Thread-sichere Bibliotheken und geeignete Entwurfsmuster stützt), oder sie sollte auf einen sehr kleinen Bereich beschränkt sein. Ihre StackQueue
-Klasse sieht einfach genug aus, so dass Sie mit ein wenig Nachdenken sicher Thread-sicher machen können.
Angenommen, die Implementierungen Stack
und Queue
sind Thread-sicher (ich kenne die Bibliotheken von .NET nicht), müssen Sie Next()
thread-safe machen. Count
ist bereits threadsicher, da kein Client den von ihm zurückgegebenen Wert ohne das clientbasierte Sperren verwenden kann - Statusabhängigkeiten zwischen den Methoden würde sonst den Code brechen.
Next()
ist nicht Thread-sicher, da es zwischen den Methoden Statusabhängigkeiten gibt. Wenn die Threads T1 und T2 stack.Count
gleichzeitig aufrufen und 1 zurückgibt, erhält einer von ihnen den Wert mit stack.Pop()
, der andere ruft stack.Pop()
auf, wenn der Stack leer ist (was dann zu werfen scheint %Code%). Sie benötigen einen Stack und eine Warteschlange mit nicht blockierenden Versionen von InvalidOperationException
und Pop()
(solche, die im leeren Zustand null zurückgeben). Dann wäre der Code Thread-sicher, wenn er folgendermaßen geschrieben wird:
Nun können Sie normalerweise Dinge wie ManualResetEvent
verwenden, um einige Threads in einen erwarteten Problemzustand zu bringen, bevor Sie das Gate freigeben ... aber das deckt nur eine kleine Teilmenge von Threading-Problemen ab.
Für das größere Problem des Threading-Bugs gibt es CHESS (in Bearbeitung) - vielleicht eine Option in der Zukunft.
Sie sollten in einem Komponententest nicht wirklich auf Fadensicherheit prüfen. Sie sollten wahrscheinlich einen separaten Satz von Stresstests für die Fadensicherheit haben.
Okay, nachdem Sie den Code veröffentlicht haben:
%Vor%Dies ist ein großartiges Beispiel dafür, wo Sie Schwierigkeiten haben werden, einen Komponententest zu schreiben, der Threading-Probleme in Ihrem Code aufdeckt. Dieser Code benötigt möglicherweise eine Synchronisierung, da sich die Werte von this.queue.Count und this.stack.Count in der Mitte der Berechnung des Gesamtwerts ändern können, sodass ein Wert zurückgegeben werden kann, der nicht "korrekt" ist.
JEDOCH: Angesichts des Rests der Klassendefinition hängt nichts wirklich davon ab, dass Count ein konsistentes Ergebnis liefert, also ist es wirklich wichtig, ob es "falsch" ist? Es gibt keine Möglichkeit, das zu wissen, ohne zu wissen, wie andere Klassen in Ihrem Programm dieses verwenden. Dadurch wird das Testen von Threading-Problemen eher zu einem Integrationstest als zu einem Komponententest.
TDD ist ein Werkzeug - und ein gutes - aber manchmal haben Sie Probleme, die mit einem bestimmten Werkzeug nicht gut gelöst werden. Ich würde vorschlagen, dass, wenn die Entwicklung der Tests übermäßig komplex ist, Sie TDD verwenden sollten, um die erwartete Funktionalität zu entwickeln, aber sich vielleicht auf Codeüberprüfung verlassen, um sich zu versichern, dass der von Ihnen hinzugefügte Sperrcode geeignet ist und Ihre Klasse Thread zulässt -safe.
Eine mögliche Lösung wäre, den Code zu entwickeln, der in das Schloss passt und es in seine eigene Methode bringt. Dann können Sie diese Methode fälschen, um Ihren Sperrcode zu testen. In Ihrem gefälschten Code können Sie einfach eine Wartezeit einrichten, um sicherzustellen, dass der zweite Thread, der auf den Code zugreift, auf die Sperre gewartet haben muss, bis der erste Thread abgeschlossen ist. Ohne genau zu wissen, was dein Code tut, kann ich nicht spezifischer sein als das.
Multithreading kann zu so komplexen Problemen führen, dass es fast unmöglich ist, Komponententests für sie zu schreiben. Sie könnten es schaffen, einen Komponententest zu schreiben, der eine 100% ige Fehlerrate hat, wenn er auf einem Code ausgeführt wird, aber nachdem Sie ihn bestanden haben, ist es ziemlich wahrscheinlich, dass es noch Race Conditions und ähnliche Probleme im Code gibt.
Das Problem mit Threading-Problemen ist, dass sie zufällig auftauchen. Selbst wenn ein Komponententest bestanden wird, bedeutet dies nicht unbedingt, dass der Code funktioniert. Also in diesem Fall gibt TDD ein falsches Gefühl der Sicherheit und könnte sogar als eine schlechte Sache betrachtet werden.
Und es ist auch daran zu erinnern, dass wenn eine Klasse threadsicher ist, Sie sie aus mehreren Threads ohne Probleme verwenden können - aber wenn eine Klasse nicht threadsicher ist, bedeutet das nicht sofort, dass Sie sie nicht aus mehreren Threads verwenden können ohne Probleme. Es könnte in der Praxis immer noch threadsicher sein, aber niemand will nur die Verantwortung dafür übernehmen, dass es nicht threadsicher ist. Und wenn es in der Praxis threadsicher ist, ist es unmöglich, einen Komponententest zu schreiben, der wegen Multi-Threading nicht funktioniert. (Natürlich sind die meisten nicht threadsicheren Klassen wirklich nicht Thread-sicher und werden glücklich ausfallen.)
Nur um klar zu sein, müssen nicht alle Klassen Schlösser threadsicher sein.
Wenn Ihre Tests zu komplex werden, kann das ein Symptom dafür sein, dass Ihre Klassen oder Methoden zu kompliziert sind, zu eng gekoppelt sind oder zu viel Verantwortung übernehmen. Versuchen Sie, dem Prinzip der einheitlichen Verantwortung zu folgen.
Würde es Ihnen etwas ausmachen, konkretere Informationen über Ihre Klasse zu veröffentlichen?
Besonders bei Mehrkernsystemen können Sie normalerweise Threading-Probleme testen, nur nicht deterministisch.
In der Regel mache ich mehrere Threads, die den fraglichen Code durchbohren und die Anzahl der unerwarteten Ergebnisse zählen. Diese Threads werden für eine kurze Zeit ausgeführt (in der Regel 2-3 Sekunden). Verwenden Sie dann Interlocked.CompareExchange, um ihre Ergebnisse hinzuzufügen und normal zu beenden. Mein Test, der sie hochgefahren hat, ruft dann .Join auf und überprüft dann, ob die Anzahl der Fehler 0 war.
Sicher, es ist nicht idiotensicher, aber mit einer Multi-Core-CPU macht es normalerweise die Arbeit gut genug, um meinen Kollegen zu zeigen, dass es ein Problem gibt, das mit dem Objekt angesprochen werden muss (vorausgesetzt, Gewindezugang).
Ich stimme den anderen Postern zu, dass Multithreading-Code vermieden werden sollte oder zumindest auf kleine Teile Ihrer Anwendung beschränkt sein sollte. Allerdings wollte ich immer noch einen Weg, diese kleinen Portionen zu testen. Ich arbeite an einem .NET-Port der MultithreadedTC-Java-Bibliothek . Mein Port heißt Ticking-Test und der Quellcode wird auf Google veröffentlicht Code.
Mit MultithreadedTC können Sie eine Testklasse mit mehreren Methoden schreiben, die mit einem TestThread-Attribut markiert sind. Jeder Thread kann auf eine bestimmte Anzahl von Ticks warten, um anzukommen, oder er kann behaupten, was er für die aktuelle Tick-Anzahl halten soll. Wenn alle aktuellen Threads geblockt sind, erhöht ein Coordinator-Thread die Tick-Anzahl und weckt alle Threads auf, die auf die nächste Tick-Anzahl warten. Wenn Sie interessiert sind, sehen Sie sich die MultithreadedTC-Übersicht für Beispiele an. MultithreadedTC wurde von einigen der gleichen Leute geschrieben, die FindBugs geschrieben haben.
Ich habe meinen Port erfolgreich in einem kleinen Projekt verwendet. Das wichtigste fehlende Feature ist, dass ich keine neu erstellten Threads während des Tests verfolgen kann.
Die meisten Komponententests werden nacheinander und nicht gleichzeitig ausgeführt, sodass keine Probleme mit Nebenläufigkeit auftreten.
Die Code-Überprüfung durch Programmierer mit Nebenläufigkeitsexpertise ist Ihre beste Wahl.
Diese bösen Nebenläufigkeitsprobleme treten oft erst dann auf, wenn Sie genug Produkt im Feld haben, um einige statistisch relevante Trends zu generieren. Sie sind manchmal unglaublich schwer zu finden, daher ist es oft am besten, den Code gar nicht erst schreiben zu müssen. Verwenden Sie nach Möglichkeit vorhandene threadsichere Bibliotheken und bewährte Entwurfsmuster.
Weitere Einzelheiten finden Sie im GOOS-Buch für die Kapitel zum Ende des Multi-Thread-Codes.
Tags und Links .net c# unit-testing multithreading tdd