FsCheck с установкой и демонтажем
Резюме
Существуют ли какие-либо события, которые можно запускать перед каждым случаем свойства, чтобы я мог запускать установку и демонтаж для каждого запуска свойства?
Полная версия
Я хочу иметь возможность тестировать парное поведение, такое как «я всегда могу получить письменные записи» или «вывод readAllLines равен вводу writeAllLines» со свойствами. Я также хочу, чтобы свойство не заботилось о том, как реализованы наборы операций (т. е. нужно ли очищать какие-либо ресурсы).
Каждый запуск свойства должен
- быть независимым от других запусков
- поддерживать состояние между вызовами операций в рамках одного запуска
- не знаю о том, как операции поддерживают состояние
- не быть утечкой ресурсов
Я использую FsCheck и Expecto. Примеры будут в Expecto, но проблема не во фреймворке.
Очень легко написать такую настройку и разборку с помощью тестов, основанных на примерах. Они принимают предсказуемый набор аргументов, поэтому я могу запускать их в оболочке, которая добавляет события до и после.
let testWithEnv setup cleanup name test =
let testWrap () =
let (api, env) = setup ()
test api
cleanup env
testCase name testWrap
То же самое нельзя сделать с тестами свойств. У них есть неизвестное количество аргументов, которые в значительной степени будут заполнены случайными данными.
Я могу довольно легко применить набор парных поведений, но любые созданные ресурсы, такие как потоки, остаются неиспользованными.
let testPropertyWithEnv setup cleanup name test =
let testWrap () =
let (api, env) = setup () // this is actually run once, but immutable so the individual runs don't leak state
test api // have to return this to pass along unapplied parameters
testProperty name testWrap
я изучил
События бегунаГлядя на то, как запускать тесты FsCheck , ближайшие хуки кажутся
-
OnStartFixture
который запускается только один раз для каждого тестового класса -
OnArguments
запускается после каждого прохода и потенциально может работать для запуска очистки
Существуют также экспериментальные функции тестирования на основе моделей , которые могут работать. Однако это кажется очень тяжелым, учитывая, что меня интересует только внешняя согласованность операций. Мне не нужен доступ к резервному состоянию.
Отказ от и встраиваниея всегда могу написать
testProperty "name" (fun arg1 arg2 ->
let (api,env) = setup ()
//test code here
cleanup env
)
но я хотел бы избежать шаблонов в каждом свойстве и разоблачения резервного состояния.
IDisposablesОдноразовые объекты также не решают проблему отсутствия установочного хука.
Более практический цикл бегаЯ искал способы запустить тесты свойств в своей оболочке, но самый маленький бегун
Check.one
предназначен для одного свойства без перехватов между запусками свойства.
Делать обертку ленивой тоже не работает
testProperty name lazy(testWithSetup)
1 ответ
На самом деле в FsCheck нет ничего, что могло бы вам помочь, и даже если бы оно было, я не думаю, что оно было бы открыто раскрыто в Expecto. Я также не думаю, что это так просто добавить на стороне FsCheck — он сказал, что рад обсудить это дальше, если вы откроете проблему в репозитории FsCheck.
В любом случае, с некоторым умным использованием частичного применения и за счет некоторого небольшого шаблона, на самом деле можно обернуть «вариативные» функции, что, я думаю, по сути, то, что вы просите.
Вот, код:
// these types are here to make the signatures look nicer
type Api = Api
type Env = Env
let testProperty k =
// call the property with "random" arguments
for i in 0..2 do
k i (char i) (string i)
let setup() =
printfn "setup ran"
(Api, Env)
let teardown Env =
printfn "teardown ran"
let test0 Api arg1 =
printfn "test0 %A" arg1
let test1 Api (arg1:int) (arg2:char) =
printfn "test1 %A %A" arg1 arg2
let test2 Api arg1 arg2 arg3 =
printfn "testFun %A %A %A" arg1 arg2 arg3
let testWithEnv (setup:unit -> Api*Env) (teardown: Env -> unit) (test: Api -> 'a) (k: 'a -> unit) :unit =
let (api, env) = setup()
k (test api)
teardown env
let (<*>) (f,k) arg =
f, (fun c -> k c arg)
let (<!>) f arg =
f, (fun k -> k arg)
let run (f, k) = f k
testProperty (fun arg1 arg2 arg3 ->
testWithEnv setup teardown test2 <!> arg1 <*> arg2 <*> arg3 |> run
)
Идея здесь в том, что вы используете операторы
<!>
и
<*>
для объединения любого количества аргументов любого типа, передайте это
testWithEnv
функцию, а затем вызвать результат. Операторы и the в основном необходимы для создания, а затем применения вариативного списка аргументов.
Все это безопасно для типов, т.е. если вы забудете передать аргумент или он имеет неправильный тип, вы получите ошибку типа, хотя, по общему признанию, это может быть не так ясно, как обычное приложение функции.
Я рекомендую вставить это в IDE и проверить типы, которые очень помогут понять, что происходит.
Есть несколько других стилей, в которых вы можете написать это. Например, с немного другим определением и функцией, которую вы можете написать что-то вроде
let (<+>) k arg =
fun c -> k c arg
let the arg =
fun k -> k arg
testWithEnv setup teardown test2 (the arg1 <+> arg2 <+> arg3)
Это заменяет
run
функция с
the
и нужен только один оператор
<+>
. Вероятно, есть и другие способы сократить это, выберите свой яд.