Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Negate arbitrary #417

Open
lmartelli opened this issue Nov 25, 2022 · 5 comments
Open

Negate arbitrary #417

lmartelli opened this issue Nov 25, 2022 · 5 comments

Comments

@lmartelli
Copy link

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

@jlink
Copy link
Collaborator

jlink commented Nov 25, 2022

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:

  • Is null part of it?
  • Empty strings?
  • Are all potential unicode chars - including non-characters and private-use-characters?

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.

@lmartelli
Copy link
Author

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

@jlink
Copy link
Collaborator

jlink commented Nov 27, 2022

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.

@lmartelli
Copy link
Author

I must confess, that although I have the intuition that it should be computable, I have no clue how to do it 😄

@jlink
Copy link
Collaborator

jlink commented Nov 30, 2022

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();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants