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 ее поддерживает.