Summary
Are there any events that can be run before every property case so that I can run setup and teardown for each run of a property?
Full version
I want to be able to test paired behaviors like "I can always fetch written records" or "output of readAllLines equals input to writeAllLines" with properties. I also want the property to not care how the operation sets are implemented (i.e. if any resources need to be cleaned up).
Each run of the property should
- be independent from other runs
- maintain state between calls of the operations within this single run
- not know about how how the operations maintain state
- not be a resource leak
I'm using FsCheck and Expecto. Examples will be in Expecto, but the problem isn't specific to the framework.
It is super easy to write this sort of setup and teardown with example-based tests. They take a predictable argument set, so I can run them in a wrapper that adds before and after events.
let testWithEnv setup cleanup name test =
let testWrap () =
let (api, env) = setup ()
test api
cleanup env
testCase name testWrap
The same can't be done with property tests. They have an unknown number of arguments that will largely be filled with random data.
I can apply the set of paired behaviors pretty easily, but any created resources like streams are left undisposed.
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
I've looked into
Runner eventsLooking at how to run FsCheck tests the closest hooks appear to be
OnStartFixture
which is only run once per test classOnArguments
is run after every pass and would potentially work to run cleanup
There is also the experimental Model-based testing features that could work. However, it seems really heavy considering I'm only concerned with the external consistency of operations. I do not want access to the backing state.
Giving up and inliningI can always write
testProperty "name" (fun arg1 arg2 ->
let (api,env) = setup ()
//test code here
cleanup env
)
but I'd like to avoid the boilerplate in every property and the exposure of the backing state.
IDisposablesDisposable objects also don't address the lack of setup hook.
More hands-on run loopI looked into ways of running the property tests in my wrapper, but the smallest runner Check.one
is for a single property, with no hooks between runs of the property.
Making the wrapper lazy also doesn't work testProperty name lazy(testWithSetup)
There isn't really anything in FsCheck to help you, and even if there was I don't think it'd be straightforwardly exposed in Expecto. I also don't think it's that straightforward to add on the FsCheck side - that said happy to discuss that further if you open an issue in the FsCheck repo.
In any case, with some clever use of partial application and at the cost of some slight boilerplate, it is in fact possible to wrap "variadic" functions, which I think is essentially what you're asking.
Behold, the code:
The idea here is that you use the operators
<!>
and<*>
to string together any number of arguments of any type, pass that to thetestWithEnv
function and then callrun
on the result. The operators and therun
are basically necessary to build up and then apply the variadic list of arguments.It's all type safe i.e. if you forget to pass an argument or it's of the wrong type, you'll get a type error, though admittedly it might not be as clear as normal function application.
I recommend pasting this in an IDE and checking out the types, that will help greatly with understanding what is going on.
There are a few other styles you can write this in. E.g. with a slightly different definition and a
the
function you can write something likeThis replaces the
run
function withthe
and needs only one operator<+>
. There's probably other ways to cut it too, pick your poison.