Гаразд - я не впевнений, чи допоможе вам наступне, бо я зробив деякі припущення щодо розробки рішення, яке може бути, а може і не бути істинним у вашому випадку. Можливо, моє "рішення" є занадто теоретичним і працює лише на штучних прикладах - я не робив жодного тестування за межами матеріалів, наведених нижче.
Крім того, я побачив би наступне вирішення, ніж реальне рішення, але, враховуючи відсутність відповідей, я думаю, що це все-таки може бути краще, ніж нічого (я продовжував спостерігати за вашим запитанням, чекаючи рішення, але не бачачи жодного повідомлення, я почав грати навколо з питанням).
Але досить сказано: Скажімо, у нас є проста служба передачі даних, яку можна використовувати для отримання цілого числа:
public interface IDataService
{
Task<int> LoadMagicInteger();
}
Проста реалізація використовує асинхронний код:
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;
}
}
Тепер проблема виникає, якщо ми використовуємо код "неправильно", як це проілюстровано цим класом. Foo
неправильно звертається Task.Result
замість того, await
щоб виводити результат, як Bar
це:
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;
}
}
Зараз нам (вам) потрібен спосіб написання тесту, який досягає успіху при дзвінку, Bar
але не вдається при виклику Foo
(принаймні, якщо я правильно зрозумів питання ;-)).
Я дозволю коду говорити; ось що я придумав (використовуючи тести Visual Studio, але він також повинен працювати з використанням NUnit):
DataServiceMock
використовує TaskCompletionSource<T>
. Це дозволяє нам встановити результат у визначеній точці тестового пробігу, що призводить до наступного тесту. Зауважте, що ми використовуємо делегата для повернення TaskCompletionSource назад у тест. Ви також можете помістити це в метод ініціалізації тесту та використовувати властивості.
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;
Що відбувається тут, це те, що ми спочатку перевіряємо, що ми можемо залишити метод без блокування (це не спрацювало б, якщо хтось звернувся Task.Result
- у цьому випадку ми зіткнулися б із тимчасовим очікуванням, оскільки результат завдання не стане доступним доти, доки метод не повернеться ).
Потім ми встановлюємо результат (тепер метод може виконати) і перевіряємо результат (всередині тесту одиниці ми можемо отримати доступ до Task.Result, оскільки ми насправді хочемо блокування).
Повний тестовий клас - BarTest
успішно і FooTest
не вдається за бажанням.
[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;
}
}
І невеличкий клас помічників для тестування на тупики / тайм-аути:
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
статей цього хлопця ?