好的-我不确定以下内容对您是否有帮助,因为我在制定解决方案时做出了一些假设,这些假设可能对您适用或不正确。也许我的“解决方案”过于理论化,仅适用于人工示例-除以下内容外,我没有做任何测试。
此外,我会看到以下解决方案比实际解决方案更有效,但考虑到缺乏答复,我认为这可能总比没有解决要好(我一直在观望您的问题,等待解决方案,但没有看到有人发布我开始玩问题)。
但是足够多了:假设我们有一个简单的数据服务,可用于检索整数:
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
文章集吗?