Open lmartelli opened 2 years ago
Would you be able to specify what negated behaviour is in the general case?
Even if I look at your supposedly obvious (?) example, some questions arise about what is part of negated behaviour:
What I can imagine is to have a base generator and then apply a filter and its negation, e.g.:
var base = strings().ascii();
var valid = base.filter(pw -> pw.chars().allMatch( c -> ...) && pw.length() >= 3 && ...);
var inValid = valid.negated();
I may be missing something, though.
Maybe it would be better and more general to think in terms of difference :
valid = strings().ascii().ofMinLength(3).ofMaxLength(10)
invalid = strings().difference(valid)
So that if for all x in strings(), x is either in valid or in invalid (but not both).
Thinking of implementation: I can imagine this to work for arbitraries of the same kind, e.g.
stringArbitrary.except(anotherStringArbitrary)
or
integerArbitrary.except(otherIntegerArbitrary)
But as soon as there's a generic thing involved, like filtering, mapping, or combining I don't see a way to implement it.
I wonder if this very restricted application is worth the effort since even the string difference implementation looks like quite involved programming to cover all the potential edge cases and pitfalls.
I must confess, that although I have the intuition that it should be computable, I have no clue how to do it 😄
The big problem from an implementation side is that there is no generic way to determine if an object of type T
could potentially be generated by an arbitrary of type Arbitrary<T>
.
So, the one thing from this issue that looks like being doable with reasonable effort is the suggestion from above:
var base = strings().ascii();
var valid = base.filter(pw -> pw.chars().allMatch( c -> ...) && pw.length() >= 3 && ...);
var inValid = valid.negated();
Testing Problem
When testing some validation rules, it's easy to come up with a custom arbitrary for the OK case, but much more complicated for the failing cases. Say you want to test
isValidUsername(String)
which must be true for any String of size 3-10 with only lowercase ASCII letters.strings().ofLength(3..10).withCharRange('a'..'z')
will do to test the case where the result should be true. But in order to to test the case where it should return false, you will have to manually split into subcases (length < 3, length > 10, and non lowercase letters)Suggested Solution
It would be super nice to be able to negate an arbitrary, maybe like this :
@ForAll("username", negated = true)
.