使用匿名异步方法调用方法时,xUnit测试挂起/死锁

时间:2016-06-02 21:11:07

标签: c# xamarin async-await xunit xunit.net

我有一个总是挂起/死锁的xUnit(2.1.0)测试。这里为了清晰和机密性而改变了类/方法名称的代码:

[Fact]
public void DocumentModificationTest()
{
    Exception ex = null;
    using (TestDependency testDependency = new TestDependency())
    {
        TestDependencyDocument testDependencyDoc = testDependency.Document;
        MockTestDependency fakeDependency = new MockTestDependency();
        try
        {
            DoStuffToTheDocument(testDependencyDoc, "fileName.doc", fakeDependency);
        }
        catch (Exception e)
        {
            ex = e;
        }
    }
    Assert.Null(ex);
}

如果我设置断点并跳过断言,我可以看到ex为null并且测试应该通过并完成,但它只是挂起而且我从未在跑步者身上看到测试成功。

这是DoStuffToTheDocument的样子:

public static void DoStuffToTheDocument(TestDependencyDocument document, string pFileName, MockTestDependency pContainer)
{
    pContainer.CheckInTheDocFirst(async () =>
    {
        //check some stuff
        //test returns early here

        //check other stuff(test never gets here)
        //await method (thus the async anonymous method)
    });
}

最后这是CheckInTheDocFirst的样子:

public void CheckInTheDocFirst(Action pConfirmAction) 
{
    pConfirmAction(); //since this is a method in a mock class only used for testing we just call the action
}

这里有什么想法吗?我的async-await范例是否存在导致此测试挂起的问题?

2 个答案:

答案 0 :(得分:2)

事实证明,这是由xUnit中的异步void测试方法支持引起的问题:https://github.com/xunit/xunit/issues/866#issuecomment-223477634

虽然您确实应该始终保持异步,但由于互操作性问题,有时这是不可行的。您无法随时更改您正在使用的所有内容的签名。

然而,关于你的断言要注意的一点是,即使异步void方法(在这种情况下是lambda)中抛出异常,它也总是证明是正确的。这是因为exception handling is different for async void

  

Async void方法具有不同的错误处理语义。当异步任务或异步任务方法抛出异常时,将捕获该异常并将其放在Task对象上。使用async void方法时,没有Task对象,因此异步void方法抛出的任何异常都将直接在async void方法启动时处于活动状态的SynchronizationContext上引发。图2说明了异步void方法抛出的异常无法自然捕获。

答案 1 :(得分:1)

当你有一个异步功能时,你应该真的一直异步。否则,您会遇到阻止同步上下文的问题,这会导致锁定。

pContainer.CheckInTheDocFirst应该是异步并返回一个Task(因为它正在使用一个返回Task对象的异步函数)。

DoStuffToDocument应该是一个返回Task的异步函数,因为它调用异步函数。

最后,测试本身也应该是一个返回任务的异步方法。

如果你在堆栈中一直运行异步,我认为你会发现它们正常工作。