Unter der Annahme, dass mit "Multithreading"-Code etwas gemeint war, das
- zustandsabhängig und veränderlich
- AND von mehreren Threads aufgerufen/verändert gleichzeitig
Mit anderen Worten: Es geht um die Prüfung benutzerdefinierte zustandsabhängige thread-sichere Klasse/Methode/Einheit - was heutzutage ein sehr seltenes Tier sein dürfte.
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 die Änderung des Zustands im gleichen Synchronisationskontext.
Heutzutage ist es einfach, kompositionsfähigen nebenläufigen und asynchronen Code zu schreiben, bei dem IO oder andere langsame Operationen in den Hintergrund verlagert werden, der gemeinsame Zustand aber in einem Synchronisationskontext aktualisiert und abgefragt wird. z.B. async/await Tasks und Rx in .NET etc. - Sie alle sind vom Design her testbar, "echte" Tasks und Scheduler können ersetzt werden, um das Testen deterministisch zu machen (was jedoch nicht in den Rahmen dieser Frage fällt).
Es mag sehr gezwungen klingen, aber dieser Ansatz funktioniert überraschend gut. Es ist möglich, ganze Anwendungen in diesem Stil zu schreiben, ohne dass ein Zustand thread-sicher gemacht werden muss (ich tue das).
Schritt 2. Wenn eine Manipulation des gemeinsamen Zustands in einem einzelnen Synchronisationskontext absolut nicht möglich ist.
Vergewissern Sie sich, dass das Rad nicht neu erfunden wird / dass es definitiv keine Standardalternative gibt, die für die Aufgabe angepasst werden kann. Es sollte wahrscheinlich sein, dass der Code sehr kohärent und in einer Einheit enthalten ist, z.B. mit einer guten Chance, dass es sich um einen Spezialfall einer standardmäßigen thread-sicheren Datenstruktur wie Hash-Map oder Collection oder was auch immer handelt.
Hinweis: Wenn der Code groß ist / sich über mehrere Klassen erstreckt UND Multithread-Zustandsmanipulationen benötigt, dann ist die Wahrscheinlichkeit sehr hoch, dass das Design nicht gut ist, überdenken Sie Schritt 1
Schritt 3. Wenn dieser Schritt erreicht ist, müssen wir prüfen unsere eigene benutzerdefinierte zustandsabhängige thread-sichere Klasse/Methode/Einheit .
Ich will ganz ehrlich sein: Ich musste noch nie richtige Tests für solchen Code schreiben. Meistens komme ich bei Schritt 1 davon, manchmal bei Schritt 2. Das letzte Mal, dass ich benutzerdefinierten Thread-sicheren Code schreiben musste, ist so viele Jahre her, dass es war, bevor ich Unit-Tests angenommen / wahrscheinlich würde ich es mit dem aktuellen Wissen sowieso nicht schreiben müssen.
Wenn ich einen solchen Code wirklich testen müsste ( endlich, die eigentliche Antwort ), dann würde ich die folgenden Dinge versuchen
-
Nicht-deterministische Belastungstests. z.B. 100 Threads gleichzeitig ausführen und prüfen, ob das Endergebnis konsistent ist. Dies ist eher typisch für Tests auf höherer Ebene/Integrationstests von Szenarien mit mehreren Benutzern, kann aber auch auf der Ebene der Einheiten verwendet werden.
-
Einige Test-'Hooks' bereitstellen, in die der Test Code einspeisen 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ögerungsgesteuerte Tests, um Threads in einer bestimmten Reihenfolge ablaufen zu lassen und Operationen durchzuführen. Streng genommen sind solche Tests auch nicht-deterministisch (es gibt eine Chance des Systems einfrieren / stop-the-world GC Sammlung, die sonst orchestriert Verzögerungen verzerren kann), auch ist es hässlich, aber erlaubt es, Haken zu vermeiden.
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?
0 Stimmen
@Andrew Grimm: stackoverflow.com/questions/11060/
0 Stimmen
stackoverflow.com/questions/4418373/
20 Stimmen
Ich denke, es ist wichtig, darauf hinzuweisen, dass diese Frage 8 Jahre alt ist und die Anwendungsbibliotheken in der Zwischenzeit einen weiten Weg zurückgelegt haben. In der "modernen Ära" (2016) kommt Multi-Thread-Entwicklung vor allem in eingebetteten Systemen vor. Wenn Sie jedoch an einer Desktop- oder Telefonanwendung arbeiten, sollten Sie sich zunächst über die Alternativen informieren. Anwendungsumgebungen wie .NET enthalten inzwischen Tools, die wahrscheinlich 90 % der üblichen Multi-Threading-Szenarien verwalten oder stark vereinfachen. (asnync/await, PLinq, IObservable, die TPL...). Multithreading-Code ist schwierig. Wenn man das Rad nicht neu erfindet, muss man es auch nicht neu testen.
0 Stimmen
Dies mag eine unpopuläre Idee sein, aber wenn Sie Ihren Code in Rust schreiben, ohne
unsafe
Blöcken kann der Compiler tatsächlich rennfreie Threadsicherheit garantieren. Für mich ist dies ein noch wichtigeres Merkmal von Rust als die Speichersicherheit, die normalerweise erwähnt wird.