10

我对异步/等待模式非常熟悉,但我遇到了一些让我觉得奇怪的行为。我确信它发生的原因是完全正当的,我很想了解这种行为。

这里的背景是我正在开发一个 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");
    }
}

瞧,它工作得很好。

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

我对我的解决方案很好,我只是想知道为什么当我尝试调用 async 时一切都变成梨形Action。我猜是因为,就运行时而言,它不是Action我只是将 lambda 塞入其中。我知道 lambda 会很高兴地分配给Actionor Func<Task>

4

1 回答 1

6

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

Action authenticate = async () => await am.Authenticate("foo", "bar");
authenticate.AssertThrowsExpectedException<LoginFailedException>();

当您调用操作时,它实际上是async void方法的即发即弃的调用:

try
{
    a();
}

立即返回,方法a()也是如此AssertThrowsExpectedException。同时,内部启动的一些活动am.Authenticate可能会在后台继续执行,可能在池线程上。am.Authenticate那里到底发生了什么取决于LoginFailedException. 我不确定单元测试执行环境的同步上下文是什么,但是如果它使用 default SynchronizationContext,那么在这种情况下,确实可能会在不同的线程上未观察到异常。

VS2012 自动支持异步单元测试,只要测试方法签名是async Task. await所以,我认为你已经通过使用和Func<T>为你的测试回答了你自己的问题。

于 2013-11-03T19:29:09.257 回答