我正在 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 的运行程序拾取它并妥善处理。
表达我上述测试的最佳方式是什么?
作为 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 @> }
不幸的是(据我所知[这并没有说太多,因此是问题]),最干净的可能是这样使用
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 }
您还可以创建自己的计算表达式来封装
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 @> }
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()