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(".")