16

次の非同期ワークフローをテストしたいと思います (NUnit+FsUnit を使用):

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

私はそれのために次のテストを書きました:

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

foo がスローされるため、単体テスト ランナーで次のスタック トレースを取得します。

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 と 2 つの選択肢の一致を取り除きながら、有用なスタック トレースを取得する方法はありますか? 非同期ワークフロー テストを構築するためのより良い方法はありますか?

4

2 に答える 2

7

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

テストの見栄えが良くなり、コードの再スローが (以前ほど) うまくいかなくなり、何か問題が発生したときにテスト ランナーで便利なスタック トレースを取得できるようになりました。

于 2013-08-13T08:06:14.573 に答える
4

少し前にドン・サイムに送ったいくつかの電子メールからの引用:

[Debug] --> [Exceptions] --> [CLR Exceptions] で [Catch First Chance Exceptions] を設定してみると、デバッグ エクスペリエンスが向上するはずです。「Just My Code」をオフにすることも役に立ちます。

右。async { ... } を使用すると、計算はスタックにバインドされないため、いくつかの場所で例外を再スローして、正しいスレッドに戻す必要があります。

Async.Catch やその他の例外処理を適切に使用することも役立ちます。

于 2013-08-13T00:57:08.467 に答える