我有一个总是挂起/死锁的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范例是否存在导致此测试挂起的问题?
答案 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的异步函数,因为它调用异步函数。
最后,测试本身也应该是一个返回任务的异步方法。
如果你在堆栈中一直运行异步,我认为你会发现它们正常工作。