806 Stimmen

Wie sollte ich Multithreading-Code in der Einheit testen?

Bisher habe ich den Albtraum des Testens von Multithreading-Code vermieden, da er mir einfach zu sehr wie ein Minenfeld erscheint. Ich möchte fragen, wie die Leute über das Testen von Code, der auf Threads für die erfolgreiche Ausführung angewiesen ist, oder einfach nur, wie die Leute über das Testen dieser Art von Fragen, die nur zeigen, wenn zwei Threads in einer bestimmten Weise interagieren gegangen sind?

Dies scheint ein wirklich zentrales Problem für die Programmierer heute, wäre es nützlich, unser Wissen auf dieser einen imho Pool.

2 Stimmen

Ich hatte vor, eine Frage zu genau diesem Thema zu stellen. Obwohl Will im Folgenden viele gute Punkte anführt, denke ich, dass wir es besser machen können. Ich stimme zu, dass es keinen einzigen "Ansatz" gibt, um das Problem sauber zu lösen. Aber "so gut wie möglich zu testen" setzt die Messlatte sehr niedrig an. Ich werde mit meinen Ergebnissen zurückkommen.

0 Stimmen

In Java: Das Paket java.util.concurrent enthält einige schlecht bekannte Klassen, die helfen können, deterministische JUnit-Tests zu schreiben. Werfen Sie einen Blick auf - CountDownLatch - Semaphor - Austauscher

0 Stimmen

Können Sie bitte einen Link zu Ihrer vorherigen Frage zu Unit-Tests angeben?

277voto

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

Der einfachste Weg, komplexen, multithreadingfähigen Anwendungscode zu testen, ist folgender: Wenn es zu komplex zum Testen ist, machen Sie es falsch. Wenn Sie eine einzige Instanz haben, auf die mehrere Threads einwirken, und Sie können keine Situationen testen, in denen diese Threads sich gegenseitig überholen, dann müssen Sie Ihren Entwurf überarbeiten. Es ist sowohl so einfach als auch so komplex.

Es gibt viele Möglichkeiten, für Multithreading zu programmieren, die verhindern, dass Threads gleichzeitig durch Instanzen laufen. Die einfachste ist, alle Objekte unveränderlich zu machen. Natürlich ist das normalerweise nicht möglich. Also müssen Sie die Stellen in Ihrem Entwurf identifizieren, an denen Threads mit derselben Instanz interagieren, und die Anzahl dieser Stellen reduzieren. Auf diese Weise isolieren Sie einige wenige Klassen, in denen Multithreading tatsächlich auftritt, und verringern so die Gesamtkomplexität der Tests Ihres Systems.

Sie müssen sich jedoch darüber im Klaren sein, dass Sie auch auf diese Weise nicht jede Situation testen können, in der zwei Threads aufeinander folgen. Dazu müssten Sie zwei Threads gleichzeitig im selben Test laufen lassen und dann genau kontrollieren, welche Zeilen sie zu einem bestimmten Zeitpunkt ausführen. Das Beste, was Sie tun können, ist, diese Situation zu simulieren. Dazu müssten Sie aber möglicherweise speziell für Tests programmieren, und das ist bestenfalls ein halber Schritt auf dem Weg zu einer echten Lösung.

Der beste Weg, Code auf Threading-Probleme zu testen, ist wahrscheinlich eine statische Analyse des Codes. Wenn Ihr Threading-Code nicht einem endlichen Satz von Thread-Safe-Mustern folgt, dann haben Sie möglicherweise ein Problem. Ich glaube, die Codeanalyse in VS enthält einige Kenntnisse über Threading, aber wahrscheinlich nicht viel.

So wie die Dinge derzeit stehen (und wahrscheinlich auch noch eine ganze Weile stehen werden), ist der beste Weg zum Testen von Multithreading-Anwendungen, die Komplexität des Threading-Codes so weit wie möglich zu reduzieren. Minimieren Sie die Bereiche, in denen Threads interagieren, testen Sie so gut wie möglich, und nutzen Sie die Codeanalyse, um Gefahrenbereiche zu identifizieren.

115voto

Theo Lenndorff Punkte 4438

Es ist schon eine Weile her, dass diese Frage gestellt wurde, aber sie ist immer noch nicht beantwortet ...

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

Es gibt einen Weg, den ich für C#-Code praktiziere. Für Unit-Tests sollten Sie in der Lage sein, zu programmieren reproduzierbar Tests, was bei Multithreading-Code die größte Herausforderung darstellt. Meine Antwort zielt also darauf ab, asynchronen Code in ein Test-Harness zu zwingen, das funktioniert synchron .

Diese Idee stammt aus dem Buch von Gerard Meszaros " xUnit Test Patterns " und heißt "Demütiges Objekt" (S. 695): Sie müssen Kernlogikcode und alles, was nach asynchronem Code riecht, voneinander trennen. Dies würde zu einer Klasse für die Kernlogik führen, die funktioniert synchron .

Dies versetzt Sie in die Lage, den Kernlogikcode in einer synchron Weise. Sie haben die absolute Kontrolle über das Timing der Aufrufe, die Sie an der Kernlogik vornehmen, und können somit reproduzierbar Tests. Und das ist Ihr Gewinn aus der Trennung von Kernlogik und asynchroner Logik.

Diese Kernlogik muss von einer anderen Klasse umschlossen werden, die dafür verantwortlich ist, Aufrufe an die Kernlogik asynchron zu empfangen und Delegierte diese Aufrufe an die Kernlogik. Der Produktionscode wird nur über diese Klasse auf die Kernlogik zugreifen. Da diese Klasse nur Aufrufe delegieren sollte, ist sie eine sehr "dumme" Klasse ohne viel Logik. Daher können Sie Ihre Unit-Tests für diese asymmetrische Arbeitsklasse auf ein Minimum beschränken.

Alles, was darüber hinausgeht (Testen der Interaktion zwischen Klassen), sind Komponententests. Auch in diesem Fall sollten Sie in der Lage sein, die absolute Kontrolle über das Timing zu haben, wenn Sie sich an das "Humble Object"-Muster halten.

74voto

David Joyner Punkte 21019

In der Tat eine schwierige Aufgabe! In meinen (C++-)Einheitstests habe ich dies in mehrere Kategorien unterteilt, die sich an dem verwendeten Gleichzeitigkeitsmuster orientieren:

  1. Unit-Tests für Klassen, die in einem einzigen Thread arbeiten und nicht threadfähig sind - einfach, testen Sie wie gewohnt.

  2. Einheitstests für Objekte überwachen (solche, die synchronisierte Methoden im Kontrollthread des Aufrufers ausführen), die eine synchronisierte öffentliche API bereitstellen, mehrere Mock-Threads instanziieren, die die API nutzen. Konstruieren Sie Szenarien, die die internen Bedingungen des passiven Objekts testen. Fügen Sie einen länger laufenden Test ein, der im Grunde genommen über einen langen Zeitraum von mehreren Threads aus auf das Objekt einprügelt. Ich weiß, das ist unwissenschaftlich, aber es schafft Vertrauen.

  3. Einheitstests für Aktive Objekte (solche, die ihren eigenen Thread oder ihre eigenen Threads der Kontrolle kapseln) - ähnlich wie bei Nr. 2 oben mit Variationen je nach Klassendesign. Die öffentliche API kann blockierend oder nicht-blockierend sein, Aufrufer können Futures erhalten, Daten können in Warteschlangen ankommen oder müssen aus der Warteschlange entfernt werden. Hier sind viele Kombinationen möglich; White Box weg. Es sind immer noch mehrere Mock-Threads erforderlich, um Aufrufe an das zu testende Objekt zu tätigen.

Nebenbei bemerkt:

In den internen Entwicklerschulungen, die ich durchführe, lehre ich die Säulen der Gleichzeitigkeit und diese beiden Muster als primären Rahmen für das Nachdenken über und die Zerlegung von Gleichzeitigkeitsproblemen. Natürlich gibt es noch fortgeschrittenere Konzepte, aber ich habe festgestellt, dass diese Grundlagen den Ingenieuren helfen, sich nicht in der Suppe zu verlieren. Es führt auch zu Code, der, wie oben beschrieben, besser testbar ist.

64voto

Warren Dew Punkte 8502

Ich bin in den letzten Jahren mehrmals mit diesem Problem konfrontiert worden, als ich für mehrere Projekte Code zur Behandlung von Threads geschrieben habe. Ich antworte mit Verspätung, weil die meisten anderen Antworten zwar Alternativen bieten, aber die Frage nach dem Testen nicht wirklich beantworten. Meine Antwort richtet sich an die Fälle, in denen es keine Alternative zu Multithreading-Code gibt; ich gehe der Vollständigkeit halber auch auf Fragen des Code-Designs ein, bespreche aber auch Unit-Tests.

Schreiben von testbarem Multithreading-Code

Als Erstes müssen Sie den Code für die Behandlung der Produktions-Threads von dem Code trennen, der die eigentliche Datenverarbeitung durchführt. Auf diese Weise kann die Datenverarbeitung als Single-Thread-Code getestet werden, und der Multi-Thread-Code dient lediglich der Koordinierung der Threads.

Die zweite Sache, die man bedenken sollte, ist, dass Fehler in Multithreading-Code probabilistisch sind; die Fehler, die sich am wenigsten häufig manifestieren, sind die Fehler, die sich in die Produktion einschleichen werden, die selbst in der Produktion schwer zu reproduzieren sind und daher die größten Probleme verursachen werden. Aus diesem Grund ist die Standardmethode, den Code schnell zu schreiben und ihn dann zu debuggen, bis er funktioniert, eine schlechte Idee für Multithreading-Code; sie wird zu Code führen, in dem die einfachen Fehler behoben sind und die gefährlichen Fehler noch vorhanden sind.

Stattdessen müssen Sie beim Schreiben von Multithreading-Code den Code mit der Einstellung schreiben, dass Sie die Fehler von vornherein vermeiden wollen. Wenn Sie den Datenverarbeitungscode ordnungsgemäß entfernt haben, sollte der Code für die Thread-Verarbeitung klein genug sein - vorzugsweise ein paar Zeilen, schlimmstenfalls ein paar Dutzend Zeilen -, dass Sie eine Chance haben, ihn zu schreiben, ohne einen Fehler zu schreiben, und sicherlich ohne viele Fehler zu schreiben, wenn Sie Threading verstehen, sich Zeit nehmen und vorsichtig sind.

Schreiben von Unit-Tests für multithreadingfähigen Code

Sobald der Multithreading-Code so sorgfältig wie möglich geschrieben ist, lohnt es sich, Tests für diesen Code zu schreiben. Der Hauptzweck der Tests besteht nicht so sehr darin, auf stark zeitabhängige Race-Condition-Fehler zu testen - es ist unmöglich, auf solche Race-Conditions wiederholbar zu testen -, sondern vielmehr darin, zu prüfen, ob Ihre Sperrstrategie zur Vermeidung solcher Fehler die beabsichtigte Interaktion mehrerer Threads ermöglicht.

Um das korrekte Sperrverhalten zu testen, muss ein Test mehrere Threads starten. Um den Test wiederholbar zu machen, sollen die Interaktionen zwischen den Threads in einer vorhersehbaren Reihenfolge ablaufen. Wir wollen die Threads im Test nicht extern synchronisieren, da dies Fehler verdeckt, die in der Produktion auftreten könnten, wenn die Threads nicht extern synchronisiert sind. Damit bleibt nur die Verwendung von Zeitverzögerungen für die Thread-Synchronisierung, die ich immer dann erfolgreich eingesetzt habe, wenn ich Tests für Multithreading-Code schreiben musste.

Wenn die Verzögerungen zu kurz sind, wird der Test anfällig, weil geringfügige Zeitunterschiede - z. B. zwischen verschiedenen Rechnern, auf denen die Tests ausgeführt werden - dazu führen können, dass das Timing nicht stimmt und der Test fehlschlägt. In der Regel beginne ich mit Verzögerungen, die zu Testfehlern führen, erhöhe die Verzögerungen, so dass der Test auf meinem Entwicklungsrechner zuverlässig läuft, und verdopple dann die Verzögerungen, so dass der Test eine gute Chance hat, auf anderen Rechnern zu laufen. Das bedeutet, dass der Test eine makroskopische Zeitspanne in Anspruch nimmt, aber meiner Erfahrung nach kann man diese Zeitspanne mit einem sorgfältigen Testdesign auf nicht mehr als ein Dutzend Sekunden begrenzen. Da es in Ihrer Anwendung nicht sehr viele Stellen gibt, an denen Thread-Koordinationscode erforderlich ist, sollte dies für Ihre Testsuite akzeptabel sein.

Verfolgen Sie schließlich die Anzahl der Fehler, die durch Ihren Test gefunden wurden. Wenn Ihr Test eine Codeabdeckung von 80 % hat, kann man davon ausgehen, dass er etwa 80 % der Fehler abfängt. Wenn Ihr Test gut konzipiert ist, aber keine Fehler findet, ist die Wahrscheinlichkeit groß, dass Sie keine zusätzlichen Fehler haben, die erst in der Produktion auftauchen werden. Wenn der Test einen oder zwei Fehler findet, haben Sie vielleicht noch Glück. Wenn das nicht der Fall ist, sollten Sie eine sorgfältige Überprüfung oder sogar eine komplette Neuschreibung Ihres Thread-Handling-Codes in Erwägung ziehen, da es wahrscheinlich ist, dass der Code noch versteckte Fehler enthält, die sehr schwer zu finden sind, bis der Code in Produktion ist, und dann sehr schwer zu beheben sind.

27voto

ollifant Punkte 8436

Ich hatte auch große Probleme beim Testen von Code mit mehreren Threads. Dann fand ich eine wirklich coole Lösung in "xUnit Test Patterns" von Gerard Meszaros. Das Muster, das er beschreibt, heißt Bescheidenes 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.).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X