Ok - nie jestem pewien, czy poniższe informacje będą dla ciebie pomocne, ponieważ poczyniłem pewne założenia przy opracowywaniu rozwiązania, które może, ale nie musi być prawdziwe w twoim przypadku. Być może moje „rozwiązanie” jest zbyt teoretyczne i działa tylko na sztuczne przykłady - nie wykonałem żadnych testów poza tymi poniżej.
Ponadto widziałbym następujące obejście problemu zamiast prawdziwego rozwiązania, ale biorąc pod uwagę brak odpowiedzi, myślę, że może być ono lepsze niż nic (obserwowałem twoje pytanie w oczekiwaniu na rozwiązanie, ale nie widząc żadnego opublikowanego, zacząłem grać wokół problemu).
Ale dość powiedziane: powiedzmy, że mamy prostą usługę danych, której można użyć do pobrania liczby całkowitej:
public interface IDataService
{
Task<int> LoadMagicInteger();
}
Prosta implementacja wykorzystuje kod asynchroniczny:
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;
}
}
Teraz pojawia się problem, jeśli używamy kodu „niepoprawnie”, jak pokazano w tej klasie. Foo
nieprawidłowo dostęp Task.Result
zamiast await
ing wynik jak Bar
robi:
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;
}
}
To, czego teraz potrzebujemy, to sposób na napisanie testu, który się powiedzie, gdy zadzwonisz, Bar
ale nie zadzwoni podczas rozmowy Foo
(przynajmniej jeśli poprawnie zrozumiałem pytanie ;-)).
Pozwolę kodowi mówić; oto co wymyśliłem (używając testów Visual Studio, ale powinno również działać przy użyciu NUnit):
DataServiceMock
wykorzystuje TaskCompletionSource<T>
. To pozwala nam ustawić wynik w określonym punkcie w trakcie testu, co prowadzi do następnego testu. Zauważ, że używamy delegata, aby przekazać TaskCompletionSource z powrotem do testu. Możesz także umieścić to w metodzie Initialize testu i użyć właściwości.
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;
To, co się tutaj dzieje, polega na tym, że najpierw sprawdzamy, czy możemy opuścić metodę bez blokowania (nie zadziałałoby, gdyby ktoś uzyskał dostęp Task.Result
- w tym przypadku wystąpiłby limit czasu, ponieważ wynik zadania nie został udostępniony, dopóki nie zostanie zwrócona metoda ).
Następnie ustawiamy wynik (teraz metoda może zostać wykonana) i weryfikujemy wynik (w teście jednostkowym możemy uzyskać dostęp do Task.Result, ponieważ tak naprawdę chcemy, aby nastąpiło blokowanie).
Kompletna klasa testowa - BarTest
udana i FooTest
nieudana zgodnie z życzeniem.
[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;
}
}
I mała klasa pomocnicza do testowania impasu / przekroczenia limitu czasu:
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
artykułów tego faceta ?