FsCheck: переопределить генератор для типа, но только в контексте одного родительского генератора.

Кажется, я часто сталкиваюсь со случаями, когда я хочу сгенерировать какую-то сложную структуру, но специальный вариант с типом члена генерируется по-другому.

Например, рассмотрим это дерево

      type Tree<'LeafData,'INodeData> =
    | LeafNode of 'LeafData
    | InternalNode of 'INodeData * Tree<'LeafData,'INodeData> list

Я хочу генерировать такие случаи, как

  • Ни один внутренний узел не является бездетным
  • Узлов листового типа нет
  • Используется только ограниченное подмножество типов листьев

Это легко сделать, если я переопределю все поколения соответствующего дочернего типа. Проблема в том, что, похоже, register по своей сути является действием на уровне потока, и нет альтернативы gen-local.

Например, то, что я хочу, может выглядеть так

      let limitedLeafs =
  gen {
    let leafGen = Arb.generate<LeafType> |> Gen.filter isAllowedLeaf
    do! registerContextualArb (leafGen |> Arb.fromGen)
    return! Arb.generate<Tree<NodeType, LeafType>>
  }

В частности, этот пример с деревом может работать с некоторой перетасовкой творческого типа, но это не всегда возможно.

Также можно использовать какую-то рекурсивную карту, которая обеспечивает выполнение предположений, но это кажется относительно сложным, если вышеописанное возможно. Однако я могу неправильно понимать природу генераторов FsCheck.

Кто-нибудь знает, как выполнить такое переопределение gen-local?

1 ответ

Руководство FsCheck по этому поводу:

Чтобы определить генератор, который генерирует подмножество нормального диапазона значений для существующего типа, скажем, все четные целые числа, это делает свойства более читабельными, если вы определяете единичный случай объединения и регистрируете генератор для нового типа:

В качестве примера они предлагают определить произвольные четные целые числа следующим образом:

      type EvenInt = EvenInt of int with
    static member op_Explicit(EvenInt i) = i

type ArbitraryModifiers =
    static member EvenInt() = 
        Arb.from<int> 
        |> Arb.filter (fun i -> i % 2 = 0) 
        |> Arb.convert EvenInt int
        
Arb.register<ArbitraryModifiers>() |> ignore

Затем вы можете сгенерировать и протестировать деревья, листья которых являются четными целыми числами, например:

      let ``leaves are even`` (tree : Tree<EvenInt, string>) =
    let rec leaves = function
        | LeafNode leaf -> [leaf]
        | InternalNode (_, children) ->
            children |> List.collect leaves
    leaves tree
        |> Seq.forall (fun (EvenInt leaf) ->
            leaf % 2 = 0)

Check.Quick ``leaves are even``   // output: Ok, passed 100 tests.

Честно говоря, мне больше нравится ваша идея «локального переопределения гена», но я не думаю, что FsCheck ее поддерживает.

Другие вопросы по тегам