Wie soll ich Thread-Code testen?


704

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.


2
Ich dachte daran, eine Frage zu genau diesem Thema zu stellen. Während Will unten viele gute Punkte hervorhebt, denke ich, dass wir es besser machen können. Ich bin damit einverstanden, dass es keinen einzigen "Ansatz" gibt, um sauber damit umzugehen. "So gut wie möglich testen" setzt die Messlatte jedoch sehr niedrig. Ich werde mit meinen Erkenntnissen zurückkehren.
Zach Burlingame

In Java: Das Paket java.util.concurrent enthält einige schlecht bekannte Klassen, die beim Schreiben deterministischer JUnit-Tests helfen können.
Werfen

Können Sie bitte einen Link zu Ihrer vorherigen Frage zu Unit-Tests bereitstellen?
Andrew Grimm


7
Ich denke, es ist wichtig anzumerken, dass diese Frage 8 Jahre alt ist und die Anwendungsbibliotheken in der Zwischenzeit einen ziemlich langen Weg zurückgelegt haben. In der "modernen Ära" (2016) findet die Multithread-Entwicklung hauptsächlich in eingebetteten Systemen statt. Wenn Sie jedoch an einer Desktop- oder Telefon-App arbeiten, sollten Sie zuerst die Alternativen untersuchen. Anwendungsumgebungen wie .NET enthalten jetzt Tools zum Verwalten oder Vereinfachen von wahrscheinlich 90% der gängigen Multithreading-Szenarien. (asnync / await, PLinq, IObservable, die TPL ...). Multithread-Code ist schwierig. Wenn Sie das Rad nicht neu erfinden, müssen Sie es nicht erneut testen.
Paul Williams

Antworten:


245

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.


1
Die Code-Analyse ist großartig, wenn Sie sich mit einer Sprache / einem Framework befassen, die dies zulässt. EG: Findbugs finden sehr einfache und leicht gemeinsam genutzte Parallelitätsprobleme mit statischen Variablen. Was nicht gefunden werden kann, sind Singleton-Entwurfsmuster. Es wird davon ausgegangen, dass alle Objekte mehrmals erstellt werden können. Dieses Plugin ist für Frameworks wie Spring absolut unzureichend.
Zombies

3
Es gibt tatsächlich eine Heilung: aktive Objekte. drdobbs.com/parallel/prefer-using-active-objects-instead-of-n/…
Dill

6
Obwohl dies ein guter Rat ist, frage ich mich immer noch: "Wie teste ich die minimalen Bereiche, in denen mehrere Threads erforderlich sind?"
Bryan Rayner

5
"Wenn es zu komplex zum Testen ist, machen Sie es falsch" - wir müssen alle in alten Code eintauchen, den wir nicht geschrieben haben. Wie hilft diese Beobachtung genau jemandem?
Ronna

2
Statische Analyse ist wahrscheinlich eine gute Idee, aber es wird nicht getestet. Dieser Beitrag beantwortet wirklich nicht die Frage, wie man testet.
Warren Dew

96

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.


1
Aber manchmal, wenn die Threads gut zusammenarbeiten, sollte auch etwas getestet werden, oder? Auf jeden Fall werde ich die Kernlogik vom asynchronen Teil trennen, nachdem ich Ihre Antwort gelesen habe. Aber ich werde die Logik immer noch über asynchrone Schnittstellen mit einem Rückruf testen, bei dem alle Threads bearbeitet wurden.
CopperCash

Was ist mit Multiprozessorsystemen?
Technophile

65

Tatsächlich eine harte! In meinen (C ++) Unit-Tests habe ich dies nach dem verwendeten Parallelitätsmuster in mehrere Kategorien unterteilt:

  1. Unit-Tests für Klassen, die in einem einzelnen Thread ausgeführt werden und nicht threadbewusst sind - einfach, wie gewohnt testen.

  2. 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.

  3. 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.


51

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.


3
Tests können nur das Vorhandensein von Fehlern aufdecken, nicht deren Fehlen. Die ursprüngliche Frage bezieht sich auf ein 2-Thread-Problem. In diesem Fall sind möglicherweise umfassende Tests möglich, dies ist jedoch häufig nicht der Fall. Für alles, was über die einfachsten Szenarien hinausgeht, müssen Sie möglicherweise in die Kugel beißen und formale Methoden anwenden - aber überspringen Sie nicht die Komponententests! Das Schreiben von korrektem Multithread-Code ist in erster Linie schwierig, aber ein ebenso schwieriges Problem besteht darin, ihn gegen Regression zukunftssicher zu machen.
Paul Williams

4
Erstaunliche Zusammenfassung einer der am wenigsten verstandenen Möglichkeiten. Ihre Antwort ist die wirkliche Trennung, die die Leute im Allgemeinen übersehen.
Prash

1
Ein Dutzend Sekunden ist eine ziemlich lange Zeit, selbst wenn Sie nur ein paar hundert Tests dieser Länge haben ...
Toby Speight

1
@TobySpeight Die Tests sind im Vergleich zu normalen Unit-Tests lang. Ich habe festgestellt, dass ein halbes Dutzend Tests mehr als ausreichend sind, wenn der Thread-Code so einfach wie möglich gestaltet ist. Ein paar hundert Multithreading-Tests würden mit ziemlicher Sicherheit auf eine zu komplexe Threading-Anordnung hinweisen.
Warren Dew

2
Das ist ein gutes Argument, um Ihre Thread-Logik so weit wie möglich von der Funktionalität zu trennen (ich weiß, viel leichter gesagt als getan). Und wenn möglich, teilen Sie die Testsuite in "Every-Change" - und "Pre-Commit" -Sätze auf (damit Ihre minutengenauen Tests nicht zu stark beeinträchtigt werden).
Toby Speight

22

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


20

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.


16

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:

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

1
Die Wartbarkeit ist brillant - genau das, wonach ich gesucht habe!
Forge_7

14

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:

Fuzz-Test oder Fuzzing ist eine Software-Testtechnik, die zufällige Daten ("Fuzz") für die Eingaben eines Programms bereitstellt. Wenn das Programm fehlschlägt (z. B. durch Absturz oder durch Fehlschlagen integrierter Code-Zusicherungen), können die Fehler festgestellt werden. Der große Vorteil von Fuzz-Tests besteht darin, dass das Testdesign äußerst einfach und frei von Vorurteilen über das Systemverhalten ist.

...

Fuzz-Tests werden häufig in großen Softwareentwicklungsprojekten verwendet, bei denen Black-Box-Tests eingesetzt werden. Diese Projekte haben normalerweise ein Budget für die Entwicklung von Testwerkzeugen, und Fuzz-Tests sind eine der Techniken, die ein hohes Nutzen-Kosten-Verhältnis bieten.

...

Fuzz-Tests sind jedoch kein Ersatz für umfassende Tests oder formale Methoden: Sie können nur eine zufällige Stichprobe des Systemverhaltens liefern, und in vielen Fällen kann das Bestehen eines Fuzz-Tests nur zeigen, dass eine Software Ausnahmen behandelt, ohne abzustürzen, anstatt sich richtig verhalten. Fuzz-Tests können daher nur als Werkzeug zur Fehlersuche und nicht als Qualitätssicherung angesehen werden.


13

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

Einige Hinweise:

  • GroboUtils zum Ausführen mehrerer Test-Threads
  • alphaWorks Testen Sie Instrumentenklassen, um zu bewirken, dass die Verschachtelungen zwischen den Iterationen variieren
  • Erstellen Sie ein throwableFeld und checken Sie es ein tearDown(siehe Listing 1). Wenn Sie eine schlechte Ausnahme in einem anderen Thread abfangen, weisen Sie sie einfach throwable zu.
  • Ich habe die utils-Klasse in Listing 2 erstellt und fand sie von unschätzbarem Wert, insbesondere waitForVerify und waitForCondition, wodurch die Leistung Ihrer Tests erheblich gesteigert wird.
  • Nutzen Sie es AtomicBooleanin 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.
  • Stellen Sie sicher, dass Sie Ihrem Test immer eine Zeitüberschreitung geben (z. B. @Test(timeout=60*1000)), da Parallelitätstests manchmal für immer hängen bleiben, wenn sie fehlerhaft 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 <code>null</code>
 * 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. <code>null</code> 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 <code>true</code> 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());
}

2
Eine Zeitüberschreitung ist eine gute Idee, aber wenn bei einem Test eine Zeitüberschreitung auftritt, sind spätere Ergebnisse in diesem Lauf verdächtig. Beim Timeout-Test werden möglicherweise noch einige Threads ausgeführt, die Sie durcheinander bringen können.
Don Kirkby

12

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:


Der Autor bezieht sich beim Testen auf die Randomisierung. Es könnte QuickCheck sein , das in viele Sprachen portiert wurde. Sie können für die gleichzeitige System Vortrag über solche Tests sehen hier
Max

6

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.


6
Ich habe die beiden bisher veröffentlichten Artikel gelesen und fand sie nicht sehr hilfreich. Er spricht nur über die Schwierigkeiten, ohne konkrete Ratschläge zu geben. Vielleicht werden sich zukünftige Artikel verbessern.
Don Kirkby

6

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.


6

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 .


5

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

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 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.


5

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!


Sie sollten hier auch Ihre tempus-fugitBibliothek erwähnen , die helps write and test concurrent code;)
Idolon

4

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.


3

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


3
Bitte erläutern Sie die Ansätze hier, externe Links könnten in Zukunft tot sein.
Uooo

2

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:

  1. Event-Trace / Replay. Dies erfordert einen Ereignismonitor und eine Überprüfung der gesendeten Ereignisse. In einem UT-Framework würde dies das manuelle Senden der Ereignisse als Teil eines Tests und das anschließende Durchführen von Post-Mortem-Überprüfungen umfassen.
  2. Skriptfähig. Hier interagieren Sie mit dem laufenden Code mit einer Reihe von Triggern. "Auf x> foo, baz ()". Dies könnte in ein UT-Framework interpretiert werden, in dem ein Laufzeitsystem einen bestimmten Test unter einer bestimmten Bedingung auslöst.
  3. Interaktiv. Dies funktioniert in einer automatischen Testsituation offensichtlich nicht. ;)

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.


2

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, PropertyNotifyChangedEreignisse mit einer WaitCallbackoder einer solchen ConditionalWaiterUmfrage zu verwenden.

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


1

Unter "Multithread" -Code anzunehmen, war etwas gemeint, das ist

  • staatsvoll und veränderlich
  • UND wird von mehreren Threads gleichzeitig aufgerufen / geändert

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

  1. 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.

  2. 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.

  3. 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.


0

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.


0

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.


0

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.


-1

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

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

Dann kann es hilfreich sein, neue Threads zu verspotten und das ausführbare Argument nacheinander auszuführen

@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<Thread>() {
        @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 
}

-3

(wenn möglich) Verwenden Sie keine Threads, sondern Akteure / aktive Objekte. Einfach zu testen.


2
@OMTheEternity vielleicht, aber es ist immer noch die beste Antwort imo.
Dill

-5

Sie können EasyMock.makeThreadSafe verwenden, um die Testinstanz threadsicher zu machen


Dies ist überhaupt keine Möglichkeit, Multithread-Code zu testen. Das Problem ist nicht, dass der Testcode Multithreading ausführt, sondern dass Sie Code testen, der normalerweise Multithreading ausführt. Und Sie können nicht alles weg synchronisieren, weil Sie dann tatsächlich nicht mehr auf Datenrennen testen.
Bennidi
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.