2016-06-16 10 views
1

Ich habe ein Setup, bei dem das Ändern einer Variablen einer Klasse ein asynchron ausgeführtes Ereignis auslöst, das ich versuche, Unit-Test durchzuführen.Einheit testet asynchrone Ereignisse

class A { 

    Action<int> OnIntChange; 

    private int _a; 
    public int a { 
     set { 
      OnIntChange(value); 
     } 
    } 
} 

class B { 

    RaiseAsyncEvent(int value) { 
     Task.Factory.StartNew(() => {c = value;}); 
    } 

    int c; 
} 

Früher war alles synchron, so konnte ich Unit-Test ziemlich leicht:

B b = new B(); 
A.OnIntChange += b.RaiseAsyncEvent;  
A.a = 10; 
Assert.AreEqual(10, A.b.c); 

jedoch dies nicht gelingt jetzt. Ich kann ManualResetEvents nicht verwenden, da ich dieses Ereignis nicht direkt erstelle und ohne eine Umstrukturierung des Codes nicht in der Lage sein werde. Es scheint, dass meine einzige Option ist, nur einen System.Threading.Thread.Sleep() - Aufruf nach dem Aufrufen von A.a = 10 hinzuzufügen, aber ich suche nach anderen Optionen. Vorschläge?

~~~~~~~~~~ bearbeiten ~~~~~~~~

Eine mögliche Lösung, die ich an bin auf der Suche ist die Aufgabe zu haben RaiseAsyncEvent zurückkehren es schafft und ein Feld diese Einstellung Aufgabe. Etwas wie:

class B { 

    Task task; 

    RaiseAsyncEvent(int value) { 
     task = Task.Factory.StartNew(() => {c = value;}); 
    } 

    int c; 
} 


B b = new B(); 
A.OnIntChange += b.RaiseAsyncEvent; 
b.task.Wait(); 
A.a = 10; 
Assert.AreEqual(10, A.b.c); 

~~~~~~~~~~ Weitere bearbeiten ~~~~~~~~~~

Was ich dieses tun endete zu lösen veränderte B auf die folgende :

class B { 

    bool RunAsync = true; 

    RaiseAsyncEvent(int value) { 
     Task task = Task.Factory.StartNew(() => {c = value;}); 
     if(!RunAsync) { task.Wait();} 
    } 

    int c; 
} 

und den Test zu ändern:

B b = new B(); 
b.RunAsync = false; 
A.OnIntChange += b.RaiseAsyncEvent;  
A.a = 10; 
Assert.AreEqual(10, A.b.c); 

Antwort

2

Sie können einen im Konstruktor von B. eine benutzerdefinierte Version von geben TaskScheduler kann auf den Abschluss aller Aufgaben in einem Komponententest warten.

class B { 
    public B(TaskScheduler taskScheduler) 
    { 
     _taskScheduler = taskScheduler; 
    } 


    public B(): this(TaskScheduler.Default) 
    { 
    } 


    public void RaiseAsyncEvent(int value) 
    { 
     Task.Factory.StartNew(() => {c = value;}, CancellationToken.None, 
     TaskCreationOptions.DenyChildAttach, _taskScheduler); 
    } 

    TaskScheduler _taskScheduler; 

} 

ConcurrentExclusiveSchedulerPair hat die Fähigkeit, für die Fertigstellung zu warten, die wir suchen.

// Unit test 

var schedulerPair = new ConcurrentExclusiveSchedulerPair(); 

B b = new B(schedulerPair.ConcurrentScheduler); 
A.OnIntChange += b.RaiseAsyncEvent;  
A.a = 10; 

schedulerPair.Complete(); 
schedulerPair.Completion.Wait(); 

Assert.AreEqual(10, A.b.c); 

Er skaliert erweitern Szenarien mehr, wenn Sie auf mehr als einen Asynchron-Betrieb im Auge behalten müssen:

B b1 = new B(schedulerPair.ConcurrentScheduler); 
B b2 = new B(schedulerPair.ConcurrentScheduler); 
B b3 = new B(schedulerPair.ConcurrentScheduler); 

A.OnIntChange += b1.RaiseAsyncEvent;  
A.OnIntChange += b2.RaiseAsyncEvent;  
A.OnIntChange += b3.RaiseAsyncEvent;  
A.a = 10; 

schedulerPair.Complete(); 
schedulerPair.Completion.Wait(); 

Assert.AreEqual(10, b1.c); 
Assert.AreEqual(10, b2.c); 
Assert.AreEqual(10, b3.c); 

EDIT: TaskScheduler Instanz auch eine statische Eigenschaft mit gemeinsam genutzt werden kann, in diesem Fall B Konstruktorsignatur ändert sich nicht.

+0

Ich mag diese Antwort und ich markiere sie als die akzeptierte Antwort - wenn ich mich absolut um mein Problem kümmern müsste, wie ich es gefragt habe, wäre das richtig. Was ich jedoch getan habe, war, die Aufgaben synchron auszuführen, indem ich ein "RunAsync" bool-Feld auf B hinzufügte und hinzufügte, wenn (! RunAsync) { task.wait(); } in RaiseAsyncEvent und Einstellung b.RunAsync = false; in der TestInitialize. Nicht das Schönste, aber es ist eine einfache Lösung für ein wirklich kompliziertes Problem. –

+0

Wenn Sie die Konstruktorsignatur nicht ändern möchten, sollten Sie eine statische TaskScheduler-Instanz (ein Singleton) berücksichtigen, die von allen Ereignissen gemeinsam verwendet wird. Dann, anstatt die RunAsync-Eigenschaft für alle Objekte festzulegen, weisen Sie ConcurrentScheduler der statischen Eigenschaft zu. – alexm

+0

Der Code, mit dem ich arbeite, ist weitaus komplexer, als ich hier zeigen könnte. Ich implementiere tatsächlich meinen eigenen statischen CallbackManager, an den ich ein Ereignis übergebe und einen neuen Task erzeuge, der abhängig von ein paar Faktoren asynchron oder synchron läuft. speichert das Handle und behandelt die Return-Logik. Die Bool, die ich hinzufüge, erlaubt es mir, die asynchrone Logik einfach zu umgehen und zu zwingen, sie von Anfang an zu synchronisieren, so dass alle meine Komponententests bestehen bleiben. Da der Manager statisch ist, brauche ich zu Beginn des Tests nichts mehr als eine einzige Änderung an seinem RunAsync-Bool, und es wird gut laufen. –

Verwandte Themen