我对async / await模式非常熟悉,但我碰到了一些令我感到奇怪的行为。我确信这是一个完全正确的理由,为什么会这样,我很想了解这种行为。
这里的背景是我正在开发一个Windows应用商店应用程序,因为我是一个谨慎,尽职尽责的开发人员,我会对所有内容进行单元测试。我很快发现WSA不存在ExpectedExceptionAttribute
。很奇怪,对吗?好吧,没问题!我可以用扩展方法或多或少地复制行为!所以我写了这个:
public static class TestHelpers
{
// There's no ExpectedExceptionAttribute for Windows Store apps! Why must Microsoft make my life so hard?!
public static void AssertThrowsExpectedException<T>(this Action a) where T : Exception
{
try
{
a();
}
catch (T)
{
return;
}
Assert.Fail("The expected exception was not thrown");
}
}
而且,它工作得很漂亮。
所以我继续愉快地编写我的单元测试,直到我遇到一个异步方法,我想确认在某些情况下抛出异常。 “没问题,”我心想,“我可以传入一个异步的lambda!”
所以我写了这个测试方法:
[TestMethod]
public async Task Network_Interface_Being_Unavailable_Throws_Exception()
{
var webManager = new FakeWebManager
{
IsNetworkAvailable = false
};
var am = new AuthenticationManager(webManager);
Action authenticate = async () => await am.Authenticate("foo", "bar");
authenticate.AssertThrowsExpectedException<LoginFailedException>();
}
令人惊讶的是,这会引发运行时错误。它实际上崩溃了测试运行者!
我对AssertThrowsExpectedException
方法进行了重载:
public static async Task AssertThrowsExpectedException<TException>(this Func<Task> a) where TException : Exception
{
try
{
await a();
}
catch (TException)
{
return;
}
Assert.Fail("The expected exception was not thrown");
}
我调整了我的测试:
[TestMethod]
public async Task Network_Interface_Being_Unavailable_Throws_Exception()
{
var webManager = new FakeWebManager
{
IsNetworkAvailable = false
};
var am = new AuthenticationManager(webManager);
Func<Task> authenticate = async () => await am.Authenticate("foo", "bar");
await authenticate.AssertThrowsExpectedException<LoginFailedException>();
}
我的解决方案很好,我只是想知道为什么当我尝试调用异步Action
时,为什么一切都变成梨状。我猜是因为,就运行时而言,它是而不是 Action
,我只是把lambda塞入其中。我知道lambda很乐意分配到Action
或Func<Task>
。
答案 0 :(得分:6)
在第二个代码片段场景中,它可能会使测试程序崩溃并不奇怪:
Action authenticate = async () => await am.Authenticate("foo", "bar");
authenticate.AssertThrowsExpectedException<LoginFailedException>();
当你调用这个动作时,它实际上是an async void
method的发射后忘记:
try
{
a();
}
a()
立即返回,AssertThrowsExpectedException
方法也是如此。同时,在am.Authenticate
内启动的某些活动可能会在后台继续执行,可能在池线程上执行。究竟发生了什么取决于am.Authenticate
的实现,但是当这样的异步操作完成并抛出LoginFailedException
时,它可能会在以后崩溃。我不确定单元测试执行环境的同步上下文是什么,但是如果它使用默认的SynchronizationContext
,则在这种情况下,异常可能确实在不同的线程上被抛出。
只要测试方法签名为async Task
,VS2012就会自动支持异步单元测试。所以,我认为您已经使用await
和Func<T>
来回答您自己的问题。