68 Stimmen

Einheitstest für Thread-Safe-Funktionalität?

Ich habe eine Klasse und viele Unit-Tests geschrieben, aber ich habe nicht machen es Thread sicher. Nun möchte ich die Klasse thread-sicher machen, aber um es zu beweisen und TDD zu verwenden, möchte ich einige fehlgeschlagene Unit-Tests schreiben, bevor ich mit dem Refactoring beginne.

Gibt es eine gute Möglichkeit, dies zu tun?

Mein erster Gedanke ist nur ein paar Threads erstellen und machen sie alle die Klasse in eine unsichere Weise verwenden. Tun Sie dies oft genug mit genug Threads und ich bin verpflichtet, es brechen zu sehen.

1voto

surajz Punkte 3426

TestNG oder Junit mit springframeworks Testmodul (oder einer anderen Erweiterung) bietet grundlegende Unterstützung für Gleichzeitigkeitstests.

Dieser Link könnte Sie interessieren

http://www.cs.rice.edu/~javaplt/papers/pppj2009.pdf

1voto

Steven A. Lowe Punkte 59247

Sie müssen für jedes betreffende Gleichzeitigkeitsszenario einen Testfall konstruieren. Dies kann es erforderlich machen, effiziente Operationen durch langsamere Äquivalente (oder Mocks) zu ersetzen und mehrere Tests in Schleifen durchzuführen, um die Wahrscheinlichkeit von Konflikten zu erhöhen.

ohne spezifische Testfälle ist es schwierig, spezifische Tests vorzuschlagen

einige potenziell nützliche Referenzmaterialien:

1voto

Ronnie Overby Punkte 43323

Obwohl es nicht so elegant ist wie die Verwendung eines Tools wie Racer oder Chess, habe ich diese Art von Test für die Fadensicherheit verwendet:

// from linqpad

void Main()
{
    var duration = TimeSpan.FromSeconds(5);
    var td = new ThreadDangerous(); 

    // no problems using single thread (run this for as long as you want)
    foreach (var x in Until(duration))
        td.DoSomething();

    // thread dangerous - it won't take long at all for this to blow up
    try
    {           
        Parallel.ForEach(WhileTrue(), x => 
            td.DoSomething());

        throw new Exception("A ThreadDangerException should have been thrown");
    }
    catch(AggregateException aex)
    {
        // make sure that the exception thrown was related
        // to thread danger
        foreach (var ex in aex.Flatten().InnerExceptions)
        {
            if (!(ex is ThreadDangerException))
                throw;
        }
    }

    // no problems using multiple threads (run this for as long as you want)
    var ts = new ThreadSafe();
    Parallel.ForEach(Until(duration), x => 
        ts.DoSomething());      

}

class ThreadDangerous
{
    private Guid test;
    private readonly Guid ctrl;

    public void DoSomething()
    {           
        test = Guid.NewGuid();
        test = ctrl;        

        if (test != ctrl)
            throw new ThreadDangerException();
    }
}

class ThreadSafe
{
    private Guid test;
    private readonly Guid ctrl;
    private readonly object _lock = new Object();

    public void DoSomething()
    {   
        lock(_lock)
        {
            test = Guid.NewGuid();
            test = ctrl;        

            if (test != ctrl)
                throw new ThreadDangerException();
        }
    }
}

class ThreadDangerException : Exception 
{
    public ThreadDangerException() : base("Not thread safe") { }
}

IEnumerable<ulong> Until(TimeSpan duration)
{
    var until = DateTime.Now.Add(duration);
    ulong i = 0;
    while (DateTime.Now < until)
    {
        yield return i++;
    }
}

IEnumerable<ulong> WhileTrue()
{
    ulong i = 0;
    while (true)
    {
        yield return i++;
    }
}

Die Theorie besagt, dass man, wenn man einen gefährlichen Zustand eines Threads in sehr kurzer Zeit konsistent herbeiführen kann, in der Lage sein sollte, einen sicheren Zustand eines Threads herbeizuführen und diesen zu überprüfen, indem man eine relativ lange Zeit wartet, ohne eine Zustandsbeschädigung zu beobachten.

Ich gebe zu, dass dies eine primitive Vorgehensweise ist und in komplexen Szenarien nicht unbedingt hilfreich ist.

0voto

MiguelMunoz Punkte 4172

Hier ist mein Ansatz. Bei diesem Test geht es nicht um Deadlocks, sondern um die Konsistenz. Ich teste eine Methode mit einem synchronisierten Block, mit Code, der in etwa wie folgt aussieht:

synchronized(this) {
  int size = myList.size();
  // do something that needs "size" to be correct,
  // but which will change the size at the end.
  ...
}

Es ist schwierig, ein Szenario zu entwerfen, das zuverlässig zu einem Fadenkonflikt führt, aber ich habe Folgendes getan.

Zunächst erstellte mein Unit-Test 50 Threads, startete sie alle gleichzeitig und ließ sie alle meine Methode aufrufen. Ich verwende ein CountDown Latch, um sie alle gleichzeitig zu starten:

CountDownLatch latch = new CountDownLatch(1);
for (int i=0; i<50; ++i) {
  Runnable runner = new Runnable() {
    latch.await(); // actually, surround this with try/catch InterruptedException
    testMethod();
  }
  new Thread(runner, "Test Thread " +ii).start(); // I always name my threads.
}
// all threads are now waiting on the latch.
latch.countDown(); // release the latch
// all threads are now running the test method at the same time.

Dies kann zu einem Konflikt führen, muss es aber nicht. Meine testMethod() sollte in der Lage sein, eine Ausnahme zu erzeugen, wenn ein Konflikt auftritt. Aber wir können noch nicht sicher sein, dass dies zu einem Konflikt führen wird. Wir wissen also nicht, ob der Test gültig ist. Hier ist also der Trick: Kommentieren Sie Ihr(e) synchronisierte(s) Schlüsselwort(e) aus und führen Sie den Test durch. Wenn dies zu einem Konflikt führt, schlägt der Test fehl. Wenn er ohne das Schlüsselwort synchronized fehlschlägt, ist Ihr Test gültig.

Das habe ich getan, und mein Test ist nicht fehlgeschlagen, also war es (noch) kein gültiger Test. Aber ich konnte zuverlässig einen Fehler erzeugen, indem ich den obigen Code in eine Schleife einfügte und ihn 100 Mal hintereinander ausführte. Ich rufe die Methode also 5000 Mal auf. (Ja, das wird einen langsamen Test ergeben. Machen Sie sich darüber keine Sorgen. Ihre Kunden werden sich daran nicht stören, also sollten Sie es auch nicht.)

Sobald ich diesen Code in eine äußere Schleife einfügte, konnte ich zuverlässig einen Fehler bei etwa der 20sten Iteration der äußeren Schleife erkennen. Jetzt war ich sicher, dass der Test gültig war, und ich stellte die synchronisierten Schlüsselwörter wieder her, um den eigentlichen Test durchzuführen. (Es funktionierte.)

Möglicherweise stellen Sie fest, dass der Test auf einem Rechner gültig ist und auf einem anderen nicht. Wenn der Test auf einem Rechner gültig ist und Ihre Methoden den Test bestehen, dann sind sie vermutlich auf allen Rechnern thread-sicher. Sie sollten jedoch die Gültigkeit auf dem Rechner prüfen, auf dem Ihre nächtlichen Unit-Tests ausgeführt werden.

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