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.

21voto

Dror Helper Punkte 29647

Es gibt zwei Produkte, die Ihnen dabei helfen können:

Beide prüfen auf Deadlocks in Ihrem Code (via Unit Test) und ich glaube, Chess prüft auch auf Race Conditions.

Die Verwendung beider Tools ist einfach - Sie schreiben einen einfachen Unit-Test und führen Ihren Code mehrmals aus, um zu prüfen, ob Deadlocks/Race Conditions in Ihrem Code möglich sind.

Bearbeiten: Google hat ein Tool veröffentlicht, das während der Laufzeit (nicht während der Tests) auf Wettlaufbedingungen prüft. thread-race-test .
Es wird nicht alle Race Conditions finden, da es nur den aktuellen Lauf analysiert und nicht alle möglichen Szenarien wie das obige Tool, aber es könnte Ihnen helfen, die Race Condition zu finden, sobald sie auftritt.

Aktualisierung: Die Typemock-Website enthielt keinen Link zu Racer mehr, und sie wurde in den letzten 4 Jahren nicht aktualisiert. Ich vermute, das Projekt wurde geschlossen.

10voto

Max Galkin Punkte 16522

Das Problem ist, dass die meisten Multi-Threading-Probleme, wie z.B. Race Conditions, von Natur aus nicht-deterministisch sind. Sie können von einem Hardwareverhalten abhängen, das Sie unmöglich emulieren oder auslösen können.

Das heißt, selbst wenn Sie Tests mit mehreren Threads durchführen, werden diese nicht durchweg fehlschlagen, wenn Sie einen Fehler in Ihrem Code haben.

5voto

jerryjvl Punkte 18807

Beachten Sie, dass die Antwort von Dror dies nicht explizit sagt, aber zumindest Chess (und wahrscheinlich auch Racer) arbeiten, indem sie eine Reihe von Threads durch alle möglichen Verschachtelungen laufen lassen, um wiederholbare Fehler zu erhalten. Sie lassen die Threads nicht einfach eine Zeit lang laufen und hoffen, dass ein Fehler zufällig auftritt.

Chess zum Beispiel durchläuft alle Verschachtelungen und gibt Ihnen dann einen Tag-String, der die Verschachtelung repräsentiert, bei der ein Deadlock gefunden wurde, so dass Sie Ihre Tests mit den spezifischen Verschachtelungen verknüpfen können, die aus der Perspektive des Deadlocking interessant sind.

Ich kenne die genaue interne Funktionsweise dieses Tools nicht und weiß nicht, wie es diese Tag-Strings dem Code zuordnet, den Sie möglicherweise ändern, um einen Deadlock zu beheben, aber da haben Sie es... Ich freue mich wirklich darauf, dass dieses Tool (und Pex) Teil der VS IDE wird.

3voto

daramarak Punkte 5944

Ich habe gesehen, wie Leute versucht haben, dies mit Standard-Unittests zu testen, wie Sie selbst vorschlagen. Die Tests sind langsam und haben bisher kein einziges der Gleichzeitigkeitsprobleme erkannt, mit denen unser Unternehmen zu kämpfen hat.

Nach vielen Fehlschlägen und trotz meiner Liebe zu Unittests bin ich zu der Einsicht gelangt, dass Fehler bei der Gleichzeitigkeit nicht zu den Stärken von Unittests gehören. Bei Klassen, in denen Gleichzeitigkeit ein Thema ist, empfehle ich normalerweise Analyse und Überprüfung zugunsten von Unittests. Mit einem Gesamtüberblick über das System ist es in vielen Fällen möglich, Behauptungen zur Thread-Sicherheit zu beweisen/falsifizieren.

Jedenfalls würde ich mich freuen, wenn mir jemand etwas sagen könnte, das auf das Gegenteil hindeutet, also beobachte ich diese Frage genau.

2voto

Cellfish Punkte 2202

Als ich mich vor kurzem mit demselben Problem befassen musste, habe ich mir das so vorgestellt: Zunächst einmal hat Ihre bestehende Klasse eine Aufgabe, nämlich die Bereitstellung einer bestimmten Funktionalität. Es liegt nicht in der Verantwortung des Objekts, thread-sicher zu sein. Wenn es thread-sicher sein muss, sollte ein anderes Objekt verwendet werden, um diese Funktionalität bereitzustellen. Aber wenn ein anderes Objekt die Thread-Safety-Funktionalität bereitstellt, kann es nicht optional sein, weil Sie dann nicht beweisen können, dass Ihr Code Thread-Safe ist. So handhabe ich es also:

// This interface is optional, but is probably a good idea.
public interface ImportantFacade
{
    void ImportantMethodThatMustBeThreadSafe();
}

// This class provides the thread safe-ness (see usage below).
public class ImportantTransaction : IDisposable
{
    public ImportantFacade Facade { get; private set; }
    private readonly Lock _lock;

    public ImportantTransaction(ImportantFacade facade, Lock aLock)
    {
        Facade = facade;
        _lock = aLock;
        _lock.Lock();
    }

    public void Dispose()
    {
        _lock.Unlock();
    }
}

// I create a lock interface to be able to fake locks in my tests.
public interface Lock
{
    void Lock();
    void Unlock();
}

// This is the implementation I want in my production code for Lock.
public class LockWithMutex : Lock
{
    private Mutex _mutex;

    public LockWithMutex()
    {
        _mutex = new Mutex(false);
    }

    public void Lock()
    {
        _mutex.WaitOne();
    }

    public void Unlock()
    {
        _mutex.ReleaseMutex();
    }
}

// This is the transaction provider. This one should replace all your
// instances of ImportantImplementation in your code today.
public class ImportantProvider<T> where T:Lock,new()
{
    private ImportantFacade _facade;
    private Lock _lock;

    public ImportantProvider(ImportantFacade facade)
    {
        _facade = facade;
        _lock = new T();
    }

    public ImportantTransaction CreateTransaction()
    {
        return new ImportantTransaction(_facade, _lock);
    }
}

// This is your old class.
internal class ImportantImplementation : ImportantFacade
{
    public void ImportantMethodThatMustBeThreadSafe()
    {
        // Do things
    }
}

Die Verwendung von Generika ermöglicht es, in Ihren Tests eine gefälschte Sperre zu verwenden, um zu überprüfen, ob die Sperre immer genommen wird, wenn eine Transaktion erstellt wird, und nicht freigegeben wird, bis die Transaktion beendet ist. Jetzt können Sie auch überprüfen, ob die Sperre beim Aufruf Ihrer wichtigen Methode übernommen wird. Die Verwendung im Produktionscode sollte etwa so aussehen:

// Make sure this is the only way to create ImportantImplementation.
// Consider making ImportantImplementation an internal class of the provider.
ImportantProvider<LockWithMutex> provider = 
    new ImportantProvider<LockWithMutex>(new ImportantImplementation());

// Create a transaction that will be disposed when no longer used.
using (ImportantTransaction transaction = provider.CreateTransaction())
{
    // Access your object thread safe.
    transaction.Facade.ImportantMethodThatMustBeThreadSafe();
}

Indem Sie sicherstellen, dass die ImportantImplementation nicht von jemand anderem erstellt werden kann (indem Sie sie z.B. im Provider erstellen und sie zu einer privaten Klasse machen), können Sie nun beweisen, dass Ihre Klasse thread-sicher ist, da auf sie nicht ohne eine Transaktion zugegriffen werden kann und die Transaktion immer die Sperre übernimmt, wenn sie erstellt wird und sie freigibt, wenn sie entsorgt wird.

Sicherzustellen, dass die Transaktion korrekt entsorgt wird, kann schwieriger sein, und wenn dies nicht der Fall ist, kann es zu einem merkwürdigen Verhalten in Ihrer Anwendung kommen. Sie können Tools wie Microsoft Chess (wie in einer anderen Antwort vorgeschlagen) verwenden, um nach solchen Dingen zu suchen. Oder Sie können Ihren Anbieter haben die Fassade zu implementieren und machen es wie folgt implementieren:

    public void ImportantMethodThatMustBeThreadSafe()
    {
        using (ImportantTransaction transaction = CreateTransaction())
        {
            transaction.Facade.ImportantMethodThatMustBeThreadSafe();
        }
    }

Auch wenn dies die Implementierung ist, hoffe ich, dass Sie die Tests herausfinden können, um diese Klassen nach Bedarf zu überprüfen.

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