Ok - Ich bin mir nicht sicher, ob das Folgende für Sie hilfreich sein wird, da ich bei der Entwicklung einer Lösung einige Annahmen getroffen habe, die in Ihrem Fall möglicherweise zutreffen oder nicht. Vielleicht ist meine "Lösung" zu theoretisch und funktioniert nur für künstliche Beispiele - ich habe keine Tests durchgeführt, die über die folgenden Punkte hinausgehen.
Darüber hinaus würde ich das Folgende eher als Problemumgehung als als echte Lösung ansehen, aber angesichts des Mangels an Antworten denke ich, dass es immer noch besser als nichts ist (Ich habe Ihre Frage beobachtet und auf eine Lösung gewartet, aber nicht gesehen, dass eine veröffentlicht wurde. Ich habe angefangen zu spielen herum mit der Ausgabe).
Aber genug gesagt: Nehmen wir an, wir haben einen einfachen Datendienst, mit dem eine Ganzzahl abgerufen werden kann:
public interface IDataService
{
Task<int> LoadMagicInteger();
}
Eine einfache Implementierung verwendet asynchronen Code:
public sealed class CustomDataService
: IDataService
{
public async Task<int> LoadMagicInteger()
{
Console.WriteLine("LoadMagicInteger - 1");
await Task.Delay(100);
Console.WriteLine("LoadMagicInteger - 2");
var result = 42;
Console.WriteLine("LoadMagicInteger - 3");
await Task.Delay(100);
Console.WriteLine("LoadMagicInteger - 4");
return result;
}
}
Jetzt tritt ein Problem auf, wenn wir den Code "falsch" verwenden, wie in dieser Klasse dargestellt. Foo
greift Task.Result
nicht await
wie Bar
folgt auf das Ergebnis zu :
public sealed class ClassToTest
{
private readonly IDataService _dataService;
public ClassToTest(IDataService dataService)
{
this._dataService = dataService;
}
public async Task<int> Foo()
{
var result = this._dataService.LoadMagicInteger().Result;
return result;
}
public async Task<int> Bar()
{
var result = await this._dataService.LoadMagicInteger();
return result;
}
}
Was wir (Sie) jetzt brauchen, ist eine Möglichkeit, einen Test zu schreiben, der beim Anrufen erfolgreich ist, beim Anrufen Bar
jedoch fehlschlägt Foo
(zumindest, wenn ich die Frage richtig verstanden habe ;-)).
Ich werde den Code sprechen lassen; Folgendes habe ich mir ausgedacht (mit Visual Studio-Tests, aber es sollte auch mit NUnit funktionieren):
DataServiceMock
nutzt TaskCompletionSource<T>
. Auf diese Weise können wir das Ergebnis an einem definierten Punkt im Testlauf einstellen, der zum folgenden Test führt. Beachten Sie, dass wir einen Delegaten verwenden, um die TaskCompletionSource an den Test zurückzugeben. Sie können dies auch in die Initialize-Methode des Tests einfügen und Eigenschaften verwenden.
TaskCompletionSource<int> tcs = null;
this._dataService.LoadMagicIntegerMock = t => tcs = t;
Task<int> task = null;
TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Foo());
tcs.TrySetResult(42);
var result = task.Result;
Assert.AreEqual(42, result);
this._end = true;
Was hier passiert, ist, dass wir zuerst überprüfen, ob wir die Methode ohne Blockierung verlassen können (dies würde nicht funktionieren, wenn jemand darauf zugreift Task.Result
- in diesem Fall würden wir in eine Zeitüberschreitung geraten, da das Ergebnis der Aufgabe erst verfügbar gemacht wird, nachdem die Methode zurückgekehrt ist ).
Dann setzten wir das Ergebnis (jetzt kann das Verfahren durchführt) und wir das Ergebnis (in einem Gerät zu testen wir Task.Result zugreifen können , wie wir tatsächlich überprüfen wollen die Blockierung auftreten).
Vollständige Testklasse - BarTest
erfolgreich und FooTest
nicht wie gewünscht.
[TestClass]
public class UnitTest1
{
private DataServiceMock _dataService;
private ClassToTest _instance;
private bool _end;
[TestInitialize]
public void Initialize()
{
this._dataService = new DataServiceMock();
this._instance = new ClassToTest(this._dataService);
this._end = false;
}
[TestCleanup]
public void Cleanup()
{
Assert.IsTrue(this._end);
}
[TestMethod]
public void FooTest()
{
TaskCompletionSource<int> tcs = null;
this._dataService.LoadMagicIntegerMock = t => tcs = t;
Task<int> task = null;
TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Foo());
tcs.TrySetResult(42);
var result = task.Result;
Assert.AreEqual(42, result);
this._end = true;
}
[TestMethod]
public void BarTest()
{
TaskCompletionSource<int> tcs = null;
this._dataService.LoadMagicIntegerMock = t => tcs = t;
Task<int> task = null;
TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Bar());
tcs.TrySetResult(42);
var result = task.Result;
Assert.AreEqual(42, result);
this._end = true;
}
}
Und eine kleine Helferklasse zum Testen auf Deadlocks / Timeouts:
public static class TaskTestHelper
{
public static void AssertDoesNotBlock(Action action, int timeout = 1000)
{
var timeoutTask = Task.Delay(timeout);
var task = Task.Factory.StartNew(action);
Task.WaitAny(timeoutTask, task);
Assert.IsTrue(task.IsCompleted);
}
}
async
gelesen ?