2014-09-01 3 views
6

Я пишу код F # и тесты в xUnit 1.9.Тестирование F # async рабочих процессов с поддержкой поддержки xUnit.net

Для нормальной работы с синхронизацией я просто возвращаю unit, и все хорошо; но теперь я переношу синхронизацию, чтобы быть рабочими потоками async.

Другими словами, мой простой AAA становится явным Async использование выталкивается в него, как я реорганизовать систему:

let [<Fact>] ``Can consume using NEventStore InMemory``() = 
    let store = NesGateway.createInMemory() 

    let finalDirection = playCircuit store |> Async.RunSynchronously // <----- YUCK 

    test <@ CounterClockWise = finalDirection @> 

Чтобы исправить это, я хочу, чтобы сделать тело теста быть async. Однако, насколько мне известно, xUnit.net управляет только методами, возвращающими Task -развернутыми типами, и, следовательно, мне нужен безопасный способ, чтобы мое тестовое тело async было удобно обернуто для бегуна xUnit.net, чтобы поднять его и обработать соответствующим образом ,

Каков наилучший способ выразить мой тест выше?

+1

Почему «гадость»? Что не так с этим кодом? –

+0

https://gist.github.com/mausch/8943d1b0f884f88bd8ae –

+0

@MauricioScheffer Прохладная идея. Я думал об Exude, когда увидел ваш код, прежде чем я прочитал ваше упоминание. Yuck ссылается на то, что у меня может быть тест синхронизации с явным материалом «Async», но обнаружите, что наличие теста «async», как и в вашем примере, дает что-то более чистое (одна из причин заключается в том, что тестовый код соответствует строке). Я согласен с тем, что в вырожденном случае чистого AAA большая часть мавризмы заключается в том, следует ли посыпать в «Async», вместо того, чтобы переключать тест в целом на «async». (Мне нравится идея и внешний вид много Fuchu и тому подобное, но «еще нет» :) –

ответ

8

В рамках XUnit 2,2 Beta 3, Уилсон @ Брэд сделал the necessary to make it work natively directly. Поэтому теперь можно просто написать:

let [<Fact>] ``Can consume NEventStore InMemory``() = async { 
    let store = NesGateway.createInMemory() 

    let! finalDirection = playCircuit store 

    test <@ CounterClockWise = finalDirection @> } 
5

К сожалению (в меру моих знаний [который не говорит много, поэтому вопрос]), чистейшее, что это возможно, чтобы использовать Async.StartAsTask <| async applelation Thusly:

let [<Fact>] ``Can consume NEventStore InMemory``() = Async.StartAsTask <| async { 
    let store = NesGateway.createInMemory() 

    let! finalDirection = playCircuit store 

    test <@ CounterClockWise = finalDirection @> } 

Или, для немного большей безопасности (в асинхронном [Fact] не будут признаны в качестве синхронизации с помощью xUnit.net, если тип возвращаемого не [получен из] Task) и [возможно] чистота (читает лучше):

// Ensure we match the return type xUnit.net is looking for 
let toFact computation : Task = Async.StartAsTask computation :> _ 

let [<Fact>] ``Can play a circuit using GES``() = toFact <| async { 
    use! store = createStore() 
    let! finalDirection = playCircuit store 
    CounterClockWise =! finalDirection } 
+0

Да, это намного лучше, чем принятый ответ, потому что он не блокирует потоки (и вызывает потенциальные взаимоблокировки). –

+0

@BradWilson неприемлемый другой ответ; оставляя это неприемлемым, так как было бы неплохо иметь какой-то быстрый способ, не связанный с помощником. –

+1

Существует открытая проблема: https://github.com/xunit/xunit/issues/955 –

2

Вы также можете создать собственное выражение вычисления инкапсулировать Async.RunSynchronously вызова:

type AsyncTestBuilder() = 
    member this.Bind (v: Async<'a>, c: 'a -> 'b) = v |> Async.RunSynchronously |> c; 
    member this.Zero() =() 

let asyncTest = AsyncTestBuilder() 

let [<Fact>] ``Can consume using NEventStore InMemory``() = asyncTest { 
    let store = NesGateway.createInMemory() 

    let! finalDirection = playCircuit store // <-- use let! instead of let 

    test <@ CounterClockWise = finalDirection @> } 
+0

По размышлению, не уверен, почему я принял это (кроме меня не устраивает мое). Хотя это субъективно лучше, чем мой первоначальный ответ, может быть значение (ограничение уровней обмана, которое «разрешено» встроить в тестовое тело), ​​отклонение от «AsyncBuilder» для построения вычислений «async», вероятно, лучше всего ограничено «прохладным» трюк ". Удивление, если вы когда-нибудь спустились по этой дороге на какое-то расстояние? (Я попытался извлечь из 'AsyncBuilder' с целью ограничения конечного' return'/'return!' Type [only], но он «запечатан», поэтому я сдался и просто использовал помощник 'toFact' (см. Ниже) –

+0

To честно говоря, я использую Async.RunSynchronously при модульном тестировании async-функций. Точно так же, как и в вашем вопросе. Мне нравится быть явным. –

+0

Думаю, вы не делаете так, как обманываете в своих тестах столько же, сколько меня: P –

0

Pre XUnit 2,2, вы можете использовать TaskBuilder для получения Task<T>, используя тот же стиль, как async: -

let [<Fact>] ``Can consume NEventStore InMemory``() = task { 
    let store = NesGateway.createInMemory() 

    let! finalDirection = playCircuit store 

    test <@ CounterClockWise = finalDirection @> } 

Отрывок (не принимать его, принять the full source, как вы найдете, что это актуально для всех видов Task -munging):

open System.Threading 
open System.Threading.Tasks 

[<AbstractClass>] 
type AsyncBuilderAbstract() = 
    // AsyncBuilder is marked sealed, so we need this wrapper 
    member __.Zero() = async.Zero() 
    member __.Return t = async.Return t 
    member __.ReturnFrom t = async.ReturnFrom t 
    member __.Bind(f,g) = async.Bind(f,g) 
    member __.Combine(f,g) = async.Combine(f,g) 
    member __.Delay f = async.Delay f 
    member __.While(c,b) = async.While(c,b) 
    member __.For(xs,b) = async.For(xs,b) 
    member __.TryWith(b,e) = async.TryWith(b,e) 

type TaskBuilder(?ct : CancellationToken) = 
    inherit AsyncBuilderAbstract() 
    member __.Run f : Task<'T> = Async.StartAsTask(f, ?cancellationToken = ct) 

type UntypedTaskBuilder(?ct : CancellationToken) = 
    inherit AsyncBuilderAbstract() 
    member __.Run f : Task = Async.StartAsTask(f, ?cancellationToken = ct) :> Task 

let task = new TaskBuilder() 
Смежные вопросы