2013-08-12 12 views
13

Chciałbym przetestować następujące obieg asynchronicznej (z NUnit + FsUnit):Jak uzyskać użyteczną StackTrace podczas testowania F # async przepływy pracy

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

napisałem następujący test dla niego:

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

Ponieważ foo rzuca się następujący stackTrace w biegacza testów jednostkowych:

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 

Niestety dOE stackTrace powiedz mi, gdzie został zgłoszony wyjątek. Zatrzymuje się w RunSynchronously.

Gdzieś słyszałem, że Async.Catch magicznie przywraca StackTrace, więc poprawiłem Test:

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)) 

teraz jest brzydki, ale przynajmniej produkuje użytecznej StackTrace:

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 [email protected](Unit unitVar) in ExplorationTests.fs: line 71 
    at Microsoft.FSharp.Control.AsyncBuilderImpl.[email protected](AsyncParams`1 args) 

ten czas pokazania stosu pokazuje dokładnie, gdzie wystąpił błąd: [email protected] 71

Czy istnieje sposób na pozbycie się Async.Catch i dopasowanie dwóch opcji wciąż otrzymując przydatne stosy stacków? Czy istnieje lepszy sposób na uporządkowanie asynchronicznych testów przepływu pracy?

+1

Miałem ten sam problem i Async.Złapałem jedyne obejście, jakie mogłem znaleźć. –

+0

Mam emaila do Dona Syme'a, który zasugerował, że było to podstawowe ograniczenie .NET, a opcja 'Async.Catch' była jedyną opcją. –

+0

@JohnPalmer brzmi jak odpowiedź na mnie – mydogisbox

Odpowiedz

5

Od Async.Catch i Ponowne generowanie wyjątku wydaje się być jedynym sposobem na uzyskanie użytecznego StackTrace wymyśliłem następujący:

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" 

Note „ExceptionDispatchInfo.Capture (ex) .Throw () ". To o najmilszy sposób, w jaki można ponownie rzucić wyjątek bez uszkadzania jego stacktrace (minus: dostępny tylko od .NET 4.5).

Teraz mogę przepisać „test” TestFooWithBetterStacktrace tak:

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

Test wygląda o wiele lepiej, kod Ponowne generowanie nie ssać (jak poprzednio) i uzyskać użyteczne stacktraces w teście biegacz, gdy coś pójdzie nie tak.

+0

+1 Wydaje się to miłym obejściem problemu. – Daniel

3

Cytując z niektórych wiadomości, które wysłałem Don Syme jakiś czas temu:

Doświadczenie debug powinno poprawić jeśli spróbuj ustawić „Złap Pierwsze Wyjątki Szansa” w Debug -> Wyjątki -> Wyjątki CLR . Włączenie opcji "Just My Code" może pomóc również w wyłączeniu opcji .

i

prawo. W przypadku asynchronicznego {...} obliczenia nie są powiązane z stosami, dlatego w niektórych miejscach należy ponownie wygenerować wyjątki od , aby przywrócić je do prawidłowego wątku .

Rozsądne korzystanie z Async.Catch lub inne sposoby obsługi wyjątków mogą również pomóc w uzyskaniu .

+0

cześć john, cytat nr 1 umożliwiający łamanie wyjątków pierwszej szansy naprawdę nie pomaga, ponieważ rzadko używam debuggera. Potrzebuję przydatnych tropów stosu na wyjściu jednostki testowej. zacytować # 2 oznacza to, że robię to już w moim drugim teście (TestFooWithBetterStacktrace)? – stmax

+0

@stmax - Myślę, że twój drugi test jest dobry. –