传递异步操作时出现意外行为

Dan*_*ann 10 c# mstest async-await visual-studio-2013

我对异步/等待模式非常熟悉,但我碰到了一些让我觉得奇怪的行为.我确信这是一个非常正确的理由,为什么会发生这种情况,我很想了解这种行为.

这里的背景是我正在开发一个Windows应用商店应用程序,因为我是一个谨慎,尽职尽责的开发人员,我会对所有内容进行单元测试.我很快发现ExpectedExceptionAttributeWSA不存在.很奇怪,对吗?好吧,没问题!我可以用扩展方法或多或少地复制行为!所以我写了这个:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

而且,它工作得很漂亮.

所以我继续愉快地编写我的单元测试,直到我遇到一个异步方法,我想确认在某些情况下抛出异常."没问题,"我心想,"我可以传递一个异步的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>();
}
Run Code Online (Sandbox Code Playgroud)

令人惊讶的是,这会引发运行时错误.它实际上撞击了测试跑者!

我做了一个重载我的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");
}
Run Code Online (Sandbox Code Playgroud)

我调整了我的测试:

[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>();
}
Run Code Online (Sandbox Code Playgroud)

我的解决方案很好,我只是想知道为什么当我尝试调用异步时,为什么一切都变成梨形Action.我猜是因为,就运行时而言,它不是一个Action,我只是把lambda塞入其中.我知道lambda很乐意被分配给Action或者Func<Task>.

nos*_*tio 6

在第二个代码片段场景中,它可能会使测试程序崩溃并不奇怪:

Action authenticate = async () => await am.Authenticate("foo", "bar");
authenticate.AssertThrowsExpectedException<LoginFailedException>();
Run Code Online (Sandbox Code Playgroud)

当你调用这个动作时,它实际上是一个async void方法的" 即发即忘"调用:

try
{
    a();
}
Run Code Online (Sandbox Code Playgroud)

a()回报瞬间,也是如此的AssertThrowsExpectedException方法.同时,内部启动的一些活动am.Authenticate可能会在后台继续执行,可能在池线程上执行.究竟发生了什么取决于实现am.Authenticate,但是当这样的异步操作完成并且它抛出时,它可能会在以后崩溃你的测试器LoginFailedException.我不确定单元测试执行环境的同步上下文是什么,但如果它使用默认值SynchronizationContext,则在这种情况下,异常可能确实在不同的线程上被抛出.

只要测试方法签名是,VS2012就会自动支持异步单元测试async Task.所以,我认为您已经通过使用awaitFunc<T>测试来回答您自己的问题.