Ok - Non sono sicuro che quanto segue possa esserti di aiuto, perché ho formulato alcune ipotesi nello sviluppo di una soluzione che potrebbe o meno essere vera nel tuo caso. Forse la mia "soluzione" è troppo teorica e funziona solo per esempi artificiali - non ho fatto alcun test oltre alle cose di seguito.
Inoltre, vedrei quanto segue più una soluzione alternativa che una soluzione reale, ma considerando la mancanza di risposte penso che potrebbe essere ancora meglio di niente (ho continuato a guardare la tua domanda in attesa di una soluzione, ma non vedendo uno che veniva pubblicato ho iniziato a giocare in giro con il problema).
Ma abbastanza detto: diciamo che abbiamo un semplice servizio dati che può essere utilizzato per recuperare un numero intero:
public interface IDataService
{
Task<int> LoadMagicInteger();
}
Una semplice implementazione utilizza un codice asincrono:
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;
}
}
Ora, sorge un problema, se stiamo usando il codice "in modo errato" come illustrato da questa classe. Foo
accede in modo errato Task.Result
invece di await
ing il risultato come Bar
fa:
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;
}
}
Ciò di cui noi (tu) ora abbiamo bisogno è un modo per scrivere un test che abbia successo quando chiama Bar
ma fallisce quando chiama Foo
(almeno se ho capito bene la domanda ;-)).
Lascio parlare il codice; ecco cosa mi è venuto in mente (usando i test di Visual Studio, ma dovrebbe funzionare anche con NUnit):
DataServiceMock
utilizza TaskCompletionSource<T>
. Questo ci consente di impostare il risultato in un punto definito nell'esecuzione del test che porta al seguente test. Si noti che stiamo utilizzando un delegato per restituire TaskCompletionSource al test. Puoi anche inserirlo nel metodo Initialize del test e utilizzare le proprietà.
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;
Quello che sta succedendo qui è che per prima cosa verifichiamo che possiamo lasciare il metodo senza bloccare (questo non funzionerebbe se qualcuno accedesse Task.Result
- in questo caso avremmo un timeout poiché il risultato dell'attività non è reso disponibile fino a quando il metodo non è tornato ).
Quindi, impostiamo il risultato (ora il metodo può essere eseguito) e verifichiamo il risultato (all'interno di un unit test possiamo accedere a Task.Result come vogliamo realmente che si verifichi il blocco).
Classe di test completa: ha esito BarTest
positivo e FooTest
negativo come desiderato.
[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;
}
}
E una piccola classe di supporto per testare deadlock / timeout:
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
articoli di questo ragazzo ?