作为练习,我想实现一个 2-3 指树。这应该是尝试FsCheck的基于模型的测试的绝佳机会。我决定尝试更新的实验版本。
到目前为止,我只为测试机器编写了一个命令,因为我已经无法完成这项工作——另一方面,它使帖子变得简短。完整代码可在GitHub 上获得。
open CmdQ
open Fuchu
open FsCheck
open FsCheck.Experimental
type TestType = uint16
type ModelType = ResizeArray<TestType>
type SutType = FingerTree<TestType>
let spec =
let prepend (what:TestType) =
{ new Operation<SutType, ModelType>() with
override __.Run model =
// Also tried returning the same instance.
let copy = model |> ResizeArray
copy.Insert(0, what)
copy
override __.Check(sut, model) =
let sutList = sut |> Finger.toList
let newSut = sut |> Finger.prepend what
let newSutList = newSut |> Finger.toList
let modelList = model |> Seq.toList
let areEqual = newSutList = modelList
areEqual |@ sprintf "prepend: model = %A, actual = %A (incoming was %A)" modelList newSutList sutList
override __.ToString() = sprintf "prepend %A" what
}
let create (initial:ModelType) =
{ new Setup<SutType, ModelType>() with
override __.Actual () = initial |> Finger.ofSeq
override __.Model () = initial //|> ResizeArray // Also tried this.
}
let rndNum () : Gen<TestType> = Arb.from<uint16> |> Arb.toGen
{ new Machine<SutType, ModelType>() with
override __.Setup =
rndNum()
|> Gen.listOf
|> Gen.map ResizeArray
|> Gen.map create
|> Arb.fromGen
override __.Next _ = gen {
let! cmd = Gen.elements [prepend]
let! num = rndNum()
return cmd num
}
}
[<Tests>]
let test =
[spec]
|> List.map (StateMachine.toProperty >> testProperty "Finger tree")
|> testList "Model tests"
我的理解是:运行两次以从一个元素Operation<_>.Run
构建一个。ResizeArray
然后Operation<_>.Check
使用相同的数字运行两次以插入到单个元素FingerTree<_>
中。
两次通过中的第一个。单元素树传入,添加使其成为(正确的)二元素树,在第一个命令之后与模型进行了很好的比较。
第二个命令总是失败的。Check
使用更大的ResizeList
(现在是 3 个元素)但与第一个命令中相同的单元素 Tree 调用。再添加一个元素当然不会使其大小为 3,并且测试失败。
我本来希望我需要返回更新的模型,Check
以便命令到来。但是你需要返回 aProperty
所以这是不可能的。
我完全误解了如何解决这个问题吗?应该如何编写基于模型的工作测试?