我想测试以下异步工作流(使用NUnit+FsUnit):
let foo = async {
failwith "oops"
return 42
}
我为它写了以下测试:
let [<Test>] TestFoo () =
foo
|> Async.RunSynchronously
|> should equal 42
由于foo-shows,我在单元测试运行程序中得到了以下stacktrace:
System.Exception : oops
at Microsoft.FSharp.Control.CancellationTokenOps.RunSynchronously(CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout)
at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously(FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken)
at ExplorationTests.TestFoo() in ExplorationTests.fs: line 76
不幸的是,stacktrace没有告诉我异常是在哪里引发的。它在RunSynchronously停止。
在某个地方我听说Async.Catch神奇地恢复了堆栈竞速,所以我调整了我的测试:
let [<Test>] TestFooWithBetterStacktrace () =
foo
|> Async.Catch
|> Async.RunSynchronously
|> fun x -> match x with
| Choice1Of2 x -> x |> should equal 42
| Choice2Of2 ex -> raise (new System.Exception(null, ex))
现在这是丑陋的,但至少它产生了一个有用的堆栈竞争:
System.Exception : Exception of type 'System.Exception' was thrown.
----> System.Exception : oops
at Microsoft.FSharp.Core.Operators.Raise(Exception exn)
at ExplorationTests.TestFooWithBetterStacktrace() in ExplorationTests.fs: line 86
--Exception
at Microsoft.FSharp.Core.Operators.FailWith(String message)
at ExplorationTests.foo@71.Invoke(Unit unitVar) in ExplorationTests.fs: line 71
at Microsoft.FSharp.Control.AsyncBuilderImpl.callA@769.Invoke(AsyncParams`1 args)
这一次stacktrace显示了错误发生的确切位置:ExplorationTests.foo@line71
有没有一种方法可以摆脱Async.Catch和两个选项之间的匹配,同时仍然获得有用的堆栈竞争?有没有更好的方法来构建异步工作流测试?
由于Async.Catch和重新抛出异常似乎是获得有用堆栈竞赛的唯一方法,我提出了以下内容:
type Async with
static member Rethrow x =
match x with
| Choice1Of2 x -> x
| Choice2Of2 ex -> ExceptionDispatchInfo.Capture(ex).Throw()
failwith "nothing to return, but will never get here"
注意"ExceptionDispatchInfo.Capture(ex).Sthrow()"。这是在不破坏其堆栈的情况下重新抛出异常的最好方法(缺点:仅在.NET 4.5之后可用)。
现在我可以像这样重写测试"TestFooWithBetterStacktrace":
let [<Test>] TestFooWithBetterStacktrace () =
foo
|> Async.Catch
|> Async.RunSynchronously
|> Async.Rethrow
|> should equal 42
测试看起来好多了,重新思考的代码没有那么糟糕(和以前一样),当出现问题时,我在测试运行程序中得到了有用的堆栈竞争。
引用我不久前发给Don Syme的一些电子邮件:
如果您尝试设置"Catch First",调试体验应该会有所改善调试-->异常-->CLR异常中的"Chance Exceptions"关闭"Just My Code"也会有所帮助。
和
对。使用async{…},计算不受堆栈限制,因此在某些地方需要重新抛出异常,以便将其返回到正确的线程。
明智地使用Async.Catch或其他异常处理也可以帮助