Как создать сложный объект в FsCheck?

Я хочу создать Генератор FsCheck для создания экземпляров "сложного" объекта. Под сложным я подразумеваю существующий класс в C#, который имеет ряд дочерних свойств и коллекций. Для этих свойств и коллекций, в свою очередь, должны быть сгенерированы данные.

Представьте, что этот класс был назван Menu с дочерними коллекциями Dishes и Drinks (это я придумал, так что не обращайте внимания на дерьмовый дизайн). Я хочу сделать следующее:

  • Создайте переменный номер Dishes и переменный номер Drinks.
  • Создайте экземпляры Dish и Drink с помощью FsCheck API, чтобы заполнить их свойства.
  • Задайте некоторые другие примитивные свойства экземпляра Menu с помощью FsCheck API.

Как написать генератор для этого типа экземпляра? Это плохая идея? (Я новичок в тестировании на основе свойств). Я прочитал документы, но до сих пор явно не смог все это усвоить.

Есть хороший пример для создания записи, но на самом деле это только 3 значения одного и того же типа float.


person bentayloruk    schedule 24.02.2014    source источник
comment
comment
@MauricioScheffer обе ссылки не работают =(   -  person Maslow    schedule 15.01.2015
comment
Поскольку ссылки @MauricioScheffer не работают, вот ссылка на код генератора/сокращателя DateTime ="nofollow noreferrer">github.com/fscheck/FsCheck/blob/ (предположительно, это был один из предыдущих примеров).   -  person bentayloruk    schedule 20.06.2015


Ответы (2)


Это неплохая идея - на самом деле все дело в том, что вы можете это сделать. Генераторы FsCheck полностью композиционны.

Прежде всего обратите внимание, что если у вас есть неизменяемые объекты, конструкторы которых принимают примитивные типы, как выглядит ваш напиток и блюдо, FsCheck может генерировать их из коробки (используя отражение).

let drinkArb = Arb.from<Drink>
let dishArb = Arb.from<Dish>

должен дать вам экземпляр Arbitrary, который является генератором (генерирует случайный экземпляр Drink) и уменьшателем (берет экземпляр Drink и делает его «меньше» — это помогает при отладке, особенно для составных структур, где вы получаете небольшой счетчик -пример, если ваш тест не пройден).

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

let drinksArb = Arb.Default.PositiveInt() |> Arb.convert (fun positive -> new Drinks(positive) (fun drinks -> drinks.Amount)

Вам нужно предоставить конверсии в и из в Arb.convert и presto, новый произвольный экземпляр для напитков, который поддерживает ваш инвариант. Конечно, поддерживать другие инварианты может быть не так просто.

После этого становится немного сложнее сгенерировать генератор и термоусадку одновременно из этих двух частей. Всегда начинайте с генератора, затем термоусадочная машина появится позже, если (когда) она вам понадобится. Пример @simonhdickson выглядит разумным. Если у вас есть произвольные экземпляры выше, вы можете получить доступ к их генератору, вызвав .Generator.

let drinksGen = drinksArb.Generator

Если у вас есть генераторы частей (Drink и Dish), вы действительно можете составить их вместе, как предлагает @simonhdickson:

let menuGenerator =
    Gen.map3 (fun a b c -> Menu(a,b,c)) (Gen.listOf dishGenerator) (Gen.listOf drinkGenerator) (Arb.generate<int>)

Разделяй и властвуй! В целом, взгляните на то, что intellisense на Gen дает вам некоторые идеи о том, как создавать генераторы.

person Kurt Schelfthout    schedule 25.02.2014
comment
Можете привести пример на C#? - person user3101007; 10.02.2017
comment
для простого примера С# см.: stackoverflow.com/questions/35203290/ - person Nick Acosta; 08.08.2017

Возможно, есть лучший способ описать это, но я думаю, что это может сделать то, о чем вы думаете. Каждый из типов Drink/Dish может принимать дополнительные параметры, используя тот же стиль, что и menuGenerator.

type Drink() =
    member m.X = 1

type Dish() =
    member m.Y = 2

type Menu(dishes:Dish list, drinks:Drink list, total:int) =
    member m.Dishes = dishes
    member m.Drinks = drinks
    member m.Total = total

let drinkGenerator = Arb.generate<unit> |> Gen.map (fun () -> Drink())
let dishGenerator = Arb.generate<unit> |> Gen.map (fun () -> Dish())
let menuGenerator =
    Gen.map3 (fun a b c -> Menu(a,b,c)) <| Gen.listOf dishGenerator <| Gen.listOf drinkGenerator <| Arb.generate<int>
person Simon Dickson    schedule 24.02.2014
comment
generate<unit>, map3... излишне сложно ИМХО. - person Mauricio Scheffer; 25.02.2014
comment
Правда, приведенный вами пример ссылки намного превосходит этот. - person Simon Dickson; 25.02.2014