Expecto FsCheck получает исключение переполнения стека при генерации строки

Я пытаюсь научиться правильно использовать FsCheck и интегрировать его с Expecto на данный момент. Я могу запустить тесты свойств, если я использую конфигурацию FsCheck по умолчанию, но когда я пытаюсь использовать свой собственный генератор, это вызывает исключение переполнения стека.

Вот мой генератор

type NameGen() =
    static member Name() =
        Arb.generate<string * string>
        |> Gen.where (fun (firstName, lastName) ->
            firstName.Length > 0 && lastName.Length > 0
        )
        |> Gen.map (fun (first, last) -> sprintf "%s %s" first last)
        |> Arb.fromGen
        |> Arb.convert string id

И я пытаюсь использовать это так:

let config = { FsCheckConfig.defaultConfig with arbitrary = [typeof<NameGen>] }

let propertyTests input =
    let output = toInitials input
    output.EndsWith(".")

testPropertyWithConfig config "Must end with period" propertyTests

Исключение выдается еще до того, как оно попадет в Gen.where функция

Что я делаю неправильно? Спасибо

2 ответа

Решение

Вы пытаетесь использовать генератор строк FsCheck, чтобы переопределить, как работает его генератор строк, но когда вы это сделаете, он будет рекурсивно вызывать себя, пока не исчерпает пространство стека. Это известная проблема: https://github.com/fscheck/FsCheck/issues/109

Эта альтернатива работает?

type NameGen =
    static member Name () =
        Arb.Default.NonEmptyString().Generator
        |> Gen.map (fun (NonEmptyString s) -> s)
        |> Gen.two
        |> Gen.map (fun (first, last) -> sprintf "%s %s" first last)
        |> Arb.fromGen

Вы определяете новый генератор для строки типа, но внутри этого вы используете генератор для string * string, который использует генератор для string, К сожалению, FsCheck хранит генераторы в глобальном изменяемом состоянии (возможно, по уважительной причине?), И я думаю, это означает, что генератор продолжает вызывать себя до тех пор, пока стек не переполнится.

Это можно решить, определив генератор для пользовательского типа оболочки вместо простой строки (показано ниже).

Следующая проблема, с которой вы столкнетесь, будет исключением нулевой ссылки. Начальная сгенерированная строка может быть null и вы пытаетесь получить доступ к .Length имущество. Это может быть решено с помощью String.length вместо этого функция, которая возвращает 0 за null,

С этими изменениями ваш генератор выглядит так:

type Name = Name of string

type NameGen() =
    static member Name() =
        Arb.generate<string * string>
        |> Gen.where (fun (firstName, lastName) ->
            String.length firstName > 0 && String.length lastName > 0
        )
        |> Gen.map (fun (first, last) -> sprintf "%s %s" first last)
        |> Arb.fromGen
        |> Arb.convert Name (fun (Name n) -> n)

И ваша собственность нуждается в небольшой модификации:

let propertyTests (Name input) =
    let output = toInitials input
    output.EndsWith(".")
Другие вопросы по тегам