quicktheories / QuickTheories

Property based testing for Java 8
Apache License 2.0
505 stars 51 forks source link

Generator for type with more than 2 properties #39

Open reedrosenbluth opened 6 years ago

reedrosenbluth commented 6 years ago

Is there a cleaner syntax for creating a Gen for a type with more than 2 properties than what I have below? This starts to get ugly for types with more properties...

private Gen<Cylinder> cylinders() {
    return radii().zip(heights(), (radius, height) -> {
        return colors().map(color -> new Cylinder(radius, height, color));
    }
}

private Gen<Integer> heights() {
    return integers().from(79).upToAndIncluding(1004856);
}

private Gen<Integer> radii() {
    return integers().allPositive();
}

private Gen<String> colors() {
    return Generate.pick(Arrays.asList("Red", "Green", "Blue"));
}
hcoles commented 6 years ago

Zip is overloaded so you can combine up to three Gens, so your example can be written as.

Gen<Cylinder> cs = radii().zip(heights(), colours(), (r,h,c) -> new Cylinder(r,h,c));

So things currently start to get ugly at four.

This needs to be extended to support more values but it needs to stop somewhere (6?), at which point the best solution is probably to fix the modelling of the type being generated.

I'm open to suggestions of other ways the api could allow Gens to be combined.

danwallach commented 6 years ago

You might consider having a generator for "tuples" of various arities, such that you might then write:

Gen<Cylinder> cs = tuples(radii(), heights(), colours()).map((r,h,c) -> new Cylinder(r,h,c));

This is approximately the same as your zip() method, but it seems somewhat cleaner to have all three components of a Cylinder represented at the same level, as arguments to a tuple-generator.

danwallach commented 6 years ago

Also, consider the following code snippet:

private static Gen<RGBColor> colors() {
  return doubles().between(0, 1).map((r, g, b) -> RGBColor.color(r, g, b));
}

It's super cool to be able to map a scalar onto a tuple-type like this. When you do this, however, IntelliJ helpfully highlights the lambda, saying "lambda can be replaced with a method reference". Unfortunately, because map is overloaded for lambdas of different arities, IntelliJ's helpful code rewriter generates this:

private static Gen<RGBColor> colors() {
  return doubles().between(0, 1).map((Function3<Double, Double, Double, RGBColor>) RGBColor::color);
}

Suggestion: how about having methods called map2, map3, and so forth, either in addition to or instead of the overloading of map? This would avoid the need for the verbose typecast.