Не удается заставить работать тест на основе модели

В качестве упражнения я хотел реализовать дерево из 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, так что это невозможно.

Я совершенно неправильно понял, как подойти к этому? Как должен быть написан рабочий тест на основе модели?


person primfaktor    schedule 11.10.2016    source источник


Ответы (1)


Тестирование на основе модели предполагает, что «тестируемая система» модифицируется как побочный эффект, когда Check вызывается для конкретной операции, и инициализируется для этого тестового прогона, когда вызывается Setup.Actual(). Он предназначен для работы с изменяемыми системами, такими как изменяемый объект, и этот стиль, хотя и несколько сбивающий с толку, отлично работает с такими системами.

Поскольку ваш тип дерева пальцев неизменяем, я бы посоветовал переопределить SutType на:

type SutType = Ref<FingerTree<TestType>>

и изменить остальные соответственно.

person Kurt Schelfthout    schedule 11.10.2016
comment
Хорошо знать! Это где-то упоминается в документах? Может быть, это должно быть более заметным. Спасибо. - person primfaktor; 12.10.2016
comment
Явно недостаточно заметно, если вы пропустили это :) - person Kurt Schelfthout; 13.10.2016