Хорошо, я не уверен, что следующее поможет вам, потому что я сделал некоторые предположения при разработке решения, которое может быть верным или нет в вашем случае. Возможно, мое «решение» слишком теоретическое и работает только для искусственных примеров - я не проводил никаких тестов, кроме приведенных ниже.
Кроме того, я бы увидел следующее скорее обходное решение, чем реальное решение, но, учитывая отсутствие ответов, я думаю, что оно все еще может быть лучше, чем ничего (я продолжал наблюдать за вашим вопросом, ожидая решения, но не видя сообщения, опубликованного, я начал играть вокруг с вопросом).
Но достаточно сказано: допустим, у нас есть простой сервис данных, который можно использовать для получения целого числа:
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 обратно в тест. Вы также можете поместить это в метод Initialize теста и использовать свойства.
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
статей этого парня ?