В качестве упражнения я хотел реализовать дерево из 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 элемента), но тем же одноэлементным деревом, что и в первой команде. Добавление еще одного элемента, конечно, не приводит к размеру 3, и тест не проходит.
Я бы ожидал, что мне нужно вернуть обновленную модель из Check
для поступления команд. Но вам нужно вернуть Property
, так что это невозможно.
Я совершенно неправильно понял, как подойти к этому? Как должен быть написан рабочий тест на основе модели?