我想用Task<T>
自定义扩展方法来编写C#中的异步工作流程。我想单元测试每个我将用来确保它们正常工作的基本扩展方法,因此当我开始将它们组合在一起时,行为不会有任何意外。这种测试最重要的方面之一就是确保这些方法在他们应该执行任务时运行,而不是在工作流程正在编写时立即执行任务,而只在等待组成工作流程时执行。
实际上,我的扩展方法似乎工作正常。然而,我还没有能够创建一个单元测试来测试这些方法的等待方面而不阻塞测试线程。
我使用Visual Studio 2015年,C#5,.NET 4.5.1,和NUnit 3.
代码
这里是一个这样的扩展方法,我想测试:
public static async Task<T> Let<T>(this Task<T> source, Action<T> action)
{
var result = await source;
action(result);
return result;
}
以下是我为该方法所做的测试。 (这个项目使用Shouldly它提供了NUnit的一个流畅的界面,所以你可以写x.ShouldBe(3)
意味着Assert.AreEqual(3, x)
。)
[TestFixture, Category("Task")]
public class WhenLettingTask {
private static bool sourceExecuted;
private static int sideEffectResult;
private static Task<int> Source =>
new Task<int>(() => {
sourceExecuted = true;
return 1;
});
private static readonly Action<int> SideEffect =
n => { sideEffectResult = n; };
[SetUp]
public void TestSetup() {
sourceExecuted = false;
sideEffectResult = 0;
}
[Test]
public void ShouldNotExecuteAnythingImmediately() {
var composed = Source.Let(SideEffect);
sourceExecuted.ShouldBeFalse();
sideEffectResult.ShouldBe(0);
}
[Test]
public async Task ReturnedTaskShouldExecuteInputsOnlyOnce() {
var composed = Source.Let(SideEffect);
var result = await composed; //Blocks forever
sourceExecuted.ShouldBeTrue();
sideEffectResult.ShouldBe(1);
sourceExecuted = false;
sideEffectResult = 0;
for (var i = 0; i < 10; i++) {
result = await composed;
sourceExecuted.ShouldBeFalse();
sideEffectResult.ShouldBe(0);
}
}
}
第一个测试工作正常,但在第一await
第二个块,直到永远。
研究
有趣的是,如果删除await
被测方法内,该测试将不阻塞。
public static async Task<T> Let<T>(this Task<T> source, Action<T> action)
{
T result = default(T);
action(result);
return result;
}
在寻求帮助有关异步测试,我已经看到了很多帖子推荐使用的Task.FromResult
获得异步测试的工作,但是这基本上是短路的等待方面,因为Task<T>
创建这种方式始于RanToCompletion
的状态,并且永远不需要等待。
基于斯科特张伯伦答案解析 ,我改变了Source
财产在我的测试夹具此:
private static Task<int> Source =>
Task.Run<int>(() => {
Thread.Sleep(1000);
sourceExecuted = true;
return 1;
});
Task.Run
将立即启动任务,这使我的第二次测试通过。需要Thread.Sleep
以便第一次测试仍然通过。