使用 xUnit.net 的任务支持测试 F# 异步工作流程

问题描述 投票:0回答:4

我正在 xUnit 1.9 中编写 F# 代码和测试。

对于正常的同步内容,我只需返回

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 的运行程序拾取它并妥善处理。

表达我上述测试的最佳方式是什么?

unit-testing asynchronous f# workflow xunit.net
4个回答
10
投票

作为 xUnit 2.2 Beta 3 的一部分,@Brad Wilson 已经完成了使其直接在本机工作。因此,现在可以简单地写:

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

    let! finalDirection = playCircuit store

    test <@ CounterClockWise = finalDirection @> }

5
投票

不幸的是(据我所知[这并没有说太多,因此是问题]),最干净的可能是这样使用

Async.StartAsTask <| async
苹果:

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

    let! finalDirection = playCircuit store

    test <@ CounterClockWise = finalDirection @> }

或者,为了更安全一点(如果返回类型不是[派生自]

[Fact]
,异步
Task
将不会被xUnit.net识别为同步)和[可以说]清洁(读起来更好):

// 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 }

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
投票

F# 6 之后,您可以使用内置的

task {
表达式


[F# 6 之前和] 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 @> }

摘录(不要拿它,拿完整的源代码,因为你会发现它与各种

Task
咀嚼相关):

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()
© www.soinside.com 2019 - 2024. All rights reserved.