2016-01-14 8 views
5

Wir schreiben Komponententests für asynchronen Code mit MSTest und Moq.Mocking Async Methoden

So haben wir einige Code, wie etwas aussieht:

var moq = new Mock<Foo>(); 
moq.Setup(m => m.GetAsync()) 
    .Returns(Task.FromResult(10)); 

Oder wie diese auf Projekte, die eine neuere Version von Moq haben

var moq = new Mock<Foo>(); 
moq.Setup(m => m.GetAsync()) 
    .ReturnsAsync(10); 

am Moq Blick Umsetzung ReturnsAsync:

public static IReturnsResult<TMock> ReturnsAsync<TMock, TResult>(this IReturns<TMock, Task<TResult>> mock, TResult value) where TMock : class 
{ 
    TaskCompletionSource<TResult> completionSource = new TaskCompletionSource<TResult>(); 
    completionSource.SetResult(value); 
    return mock.Returns(completionSource.Task); 
} 

Beide Methoden scheinen unter der Haube gleich zu sein. Beide erstellen eine TaskCompletionSource, rufen Sie SetResult und senden Sie die Task

So weit so gut.

Aber kurz laufende async Methoden sind optimiert, um synchron zu agieren. Dies scheint zu implizieren, dass TaskCompletionSource immer synchron ist, was auch darauf hindeutet, dass die Kontextbehandlung und alle damit verbundenen Probleme, die auftreten könnten, niemals passieren würden.

Also, wenn wir hatten einige Code, der einige async tabu der tat, wie awaits Mischen Wait() und Result, dass diese Probleme würden in Unit-Tests nicht erkannt werden.

Wäre es von Vorteil, eine Erweiterungsmethode zu erstellen, die immer die Kontrolle liefert? Etwas wie:

In diesem Fall hätten wir eine Methode, die garantiert asynchron ausgeführt wird.

Der wahrgenommene Vorteil wäre die Erkennung von schlechtem asynchronem Code. Zum Beispiel könnte es während des Komponententests irgendein Deadlocking oder Schlucken von Ausnahmen erfassen.

Ich bin mir nicht 100% sicher, dass dies der Fall ist, also würde mich wirklich interessieren, was die Community zu sagen hat.

+0

Ich würde sagen gehen für 'ReturnsYieldingAsync', aber noch mehr, um potentielle Deadlocks zu erleben, die Sie benötigen, um einen Synchronisationskontext in Ihren Testläufern zu installieren, etwas wie [' AsyncPump'] (http: // blogs. msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx). – Noseratio

+0

Ich denke, Sie sollten versuchen, einen Testfall zu schreiben, der 1. wie erwartet funktioniert und 2. Sie können einen Testfall erstellen, der exakt dem Testfall von 1 folgt, aber niemals 'ReturnsYieldingAsync' aufruft und fehlschlägt. –

+1

@Noseratio Nice finden Sie auf der 'AsyncPump'. Das Hauptproblem, auf das wir stoßen, besteht darin, dass der Test, in den hineingelaufen wird, darin besteht, dass der Synchronisationskontext des Testlaufers null ist. Austauschen der 'AsyncPump' scheint der Weg zu sein, wie hier gezeigt http: // stackoverflow.com/questions/14087257/how-to-add-Synchronisation-Kontext-zu-Async-Test-Methode – swestner

Antwort

2

Als ich vor vier Jahren zum ersten Mal talking about testing asynchronous code startete (!), Würde ich Entwickler ermutigen, entlang der asynchronen Achse für ihre Mocks zu testen. Das heißt, Test entlang der Ergebnisachse (Erfolg, Fehler) sowie der Async-Achse (Sync, Async).

Im Laufe der Zeit habe ich jedoch auf diese gelockert. Wenn es darum geht, meinen eigenen Code zu testen, teste ich wirklich nur die synchronen Erfolg/Fehler-Pfade, es sei denn, es gibt einen klaren Grund, auch den asynchronen Erfolgspfad für diesen Code zu testen. Ich kümmere mich überhaupt nicht mehr um asynchrone Fehlerprüfungen.

+0

In unserem Fall sind wir auf eine Anzahl von Instanzen von gemischten asynchronen Mustern gestoßen. Dies ist zum großen Teil ein Trainingsthema, aber auch etwas, das wir nicht übersehen können. Ihre Nito-Bibliothek hat uns geholfen, die Deadlocks konsistent zu reproduzieren. Vielen Dank! Letztendlich werden wir diese Art von Problemen wahrscheinlich durch E2E-Tests testen. – swestner

+0

Ich habe eine herausragende Neugier, die Kern der ursprünglichen Frage war. Wissen Sie, ob 'TaskCompletionSource' immer synchron läuft? Es war eine Annahme der ursprünglichen Frage, die schön wäre zu wissen. – swestner

+0

@swestern: Wenn Sie 'SetResult' (oder ähnlich) aufrufen, ist die Aufgabe abgeschlossen, bevor der Methodenaufruf zurückkehrt. Es kann jedoch immer noch Fortsetzungen dieser Aufgabe geben, die nicht abgeschlossen sind. –