簡體   English   中英

在測試F#異步工作流時如何獲得有用的堆棧跟蹤

[英]How to get a useful stacktrace when testing F# async workflows

我想測試以下異步工作流程(使用NUnit + FsUnit):

let foo = async {
  failwith "oops"
  return 42
}

我為它編寫了以下測試:

let [<Test>] TestFoo () =
  foo
  |> Async.RunSynchronously
  |> should equal 42

自從foo拋出后,我在單元測試運行器中得到以下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

不幸的是,堆棧跟蹤並沒有告訴我異常的位置。 它在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)

這次堆棧跟蹤顯示錯誤發生的確切位置:ExplorationTests.foo@line 71

有沒有辦法擺脫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).Throw()”。 這是關於可以在不破壞其堆棧跟蹤的情況下重新拋出異常的最好方法(缺點:僅在.NET 4.5之后可用)。

現在我可以像這樣重寫測試“TestFooWithBetterStacktrace”:

let [<Test>] TestFooWithBetterStacktrace () =
  foo
  |> Async.Catch
  |> Async.RunSynchronously
  |> Async.Rethrow
  |> should equal 42

測試看起來好多了,重新拋出的代碼不會吮吸(和以前一樣多),當出現問題時,我會在測試運行器中獲得有用的堆棧跟蹤。

引用一段時間我發送Don Syme的電子郵件:

如果您嘗試在Debug - > Exceptions - > CLR Exceptions中設置“Catch First Chance Exceptions”,則調試體驗應該會有所改善。 關閉“Just My Code”也可以提供幫助。

對。 使用async {...},計算不是堆棧綁定,因此需要在某些地方重新拋出異常以使它們回到正確的線程。

明智地使用Async.Catch或其他異常處理也可以提供幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM