We want to use FsCheck as part of our unit testing in continuous integration. As such deterministic and reproducible behaviour is very important for us.
FsCheck, being a random testing framework, can generate test cases that potentially sometimes break. The key is, we do not only use properties that would have to hold for necessarily every input, like say List.rev >> List.rev === id
. But rather, we do some numerics and some test cases can cause the test to break because of being badly conditioned.
The question is: how can we guarantee, that once the test succeeds it will always succeed?
So far I see the following options:
What is the idiomatic way of using FsCheck in such a setting?
some test cases can cause the test to break because of being badly conditioned.
That sounds like you need a Conditional Property:
let isOk x =
match x with
| 42 -> false
| _ -> true
let MyProperty (x:int) = isOk x ==> // check x here...
(assuming that you don't like the number 42.)
(I started writing a comment but it got so long I guess it deserved its own answer).
It's very common to test properties with FsCheck that don't hold for every input. For example, FsCheck will trivially refute your List.rev
example if you run it for list<float>
.
Numerical stability is a tricky problem in itself - there isn't any non-determinism in FsCheck to blame here(FsCheck is totally deterministic, it's just an input generator...). The "non-determinism" you're referring to may be things like bugs in floating point operations in certain processors and so on. But even in that case, wouldn't you like to know about them? And if you algorithm is numerically unstable for a class of inputs, wouldn't you like to know about it? If you don't it seems to me like you're setting yourself up for some real non-determinism...in production.
The idiomatic way to write properties that don't hold for all inputs of a given type in FsCheck is to write a generator & shrinker. You can use ==>
as a step up to that, but it doesn't scale up well to complex preconditions. You say this could turn out pretty hard - that's true in the sense that I guarantee you'll learn something about your code. A good thing!
Fixing the seed is a bad idea, except for reproducing a previously discovered bug. I mean, in practice what would you do: keep re-running the test until it passes, then fix the seed and declare "job done"?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With