npryce / Make It Easy
Test Data Builders are described in the book Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce. This library lets you write Test Data Builders with much less duplication and boilerplate code than the approach described in the book.
Download
You can download from Maven Central with the artifact coordinates:
Example
Consider the following class hierarchy. This hierarchy illustrates a couple of complicating factors: there is an abstract base class and there is a property (Fruit.ripeness) that is not set via the constructor but by an operation of the Fruit class.
public abstract class Fruit private double ripeness = 0.0; public void ripen(double amount) ripeness = Math.min(1.0, ripeness+amount); > public boolean isRipe() return ripeness == 1.0; > > public class Apple extends Fruit private int leaves; public Apple(int leaves) this.leaves = leaves; > public int numberOfLeaves() return leaves; > > public class Banana extends Fruit public final double curve; public Banana(double curve) this.curve = curve; > public double curve() return curve; > >
You can define Test Data Builders for Apples and Bananas with Make It Easy as follows:
public class FruitMakers public static final PropertyFruit,Double> ripeness = newProperty(); public static final PropertyApple, Integer> leaves = newProperty(); public static final PropertyBanana,Double> curve = newProperty(); public static final InstantiatorApple> Apple = new InstantiatorApple>() @Override public Apple instantiate(PropertyLookupApple> lookup) Apple apple = new Apple(lookup.valueOf(leaves, 2)); apple.ripen(lookup.valueOf(ripeness, 0.0)); return apple; > >; public static final InstantiatorBanana> Banana = new InstantiatorBanana>() @Override public Banana instantiate(PropertyLookupBanana> lookup) Banana banana = new Banana(lookup.valueOf(curve, 0.5)); banana.ripen(lookup.valueOf(ripeness, 0.0)); return banana; > >; >
MakerApple> appleWith2Leaves = an(Apple, with(2, leaves)); MakerApple> ripeApple = appleWith2Leaves.but(with(ripeness, 0.9)); MakerApple> unripeApple = appleWith2Leaves.but(with(ripeness, 0.125)); Apple apple1 = make(ripeApple); Apple apple2 = make(unripeApple); Banana defaultBanana = make(a(Banana)); Banana straightBanana = make(a(Banana, with(curve, 0.0))); Banana squishyBanana = make(a(Banana, with(ripeness, 1.0)));
In contrast, doing so in the style documented in Growing Object-Oriented Software, Guided by Tests would look like this:
public interface BuilderT> T build(); > public class AppleBuilder implements BuilderApple> private double ripeness = 0.0; private int leaves = 1; private AppleBuilder() <> public static AppleBuilder anApple() return new AppleBuilder(); > public Apple build() Apple apple = new Apple(leaves); apple.ripen(ripeness); return apple; > public AppleBuilder withRipeness(double ripeness) this.ripeness = ripeness; return this; > public AppleBuilder withLeaves(int leaves) this.leaves = leaves; return this; > public AppleBuilder but() return new AppleBuilder() .withRipeness(ripeness) .withLeaves(leaves); > > public class BananaBuilder implements BuilderBanana> private double ripeness = 0.0; private double curve = 0.5; private BananaBuilder() <> public static BananaBuilder aBanana() return new BananaBuilder(); > public Banana build() Banana apple = new Banana(curve); apple.ripen(ripeness); return apple; > public BananaBuilder withRipeness(double ripeness) this.ripeness = ripeness; return this; > public BananaBuilder withCurve(double curve) this.curve = curve; return this; > public BananaBuilder but() return new BananaBuilder() .withRipeness(ripeness) .withCurve(curve); > >
AppleBuilder appleWith2Leaves = anApple().withLeaves(2); AppleBuilder ripeApple = appleWith2Leaves.but().withRipeness(0.9); AppleBuilder unripeApple = appleWith2Leaves.but().withRipeness(0.125); Apple apple1 = ripeApple.build(); Apple apple2 = unripeApple.build(); Banana defaultBanana = aBanana().build(); Banana straightBanana = aBanana().withCurve(0.0).build(); Banana squishyBanana = aBanana().withRipeness(1.0).build();
As you can see, with Make It Easy you have to write a lot less duplicated and boilerplate code. What duplication there is — in the declaration of anonymous Instantiator classes, for example — can be automatically inserted and refactored by modern IDEs. (You could also factor out calls to Fruit.ripen to a private helper method, but I left them duplicated for clarity.)
The full code for this example is in the Make It Easy repository.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].
Saved searches
Use saved searches to filter your results more quickly
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
A tiny framework that makes it easy to write Test Data Builders in Java
npryce/make-it-easy
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
A tiny framework that makes it easy to write Test Data Builders in Java
Test Data Builders are described in the book Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce. This library lets you write Test Data Builders with much less duplication and boilerplate code than the approach described in the book.
You can download from Maven Central with the artifact coordinates:
Consider the following class hierarchy. This hierarchy illustrates a couple of complicating factors: there is an abstract base class and there is a property (Fruit.ripeness) that is not set via the constructor but by an operation of the Fruit class.
public abstract class Fruit < private double ripeness = 0.0; public void ripen(double amount) < ripeness = Math.min(1.0, ripeness+amount); > public boolean isRipe() < return ripeness == 1.0; > > public class Apple extends Fruit < private int leaves; public Apple(int leaves) < this.leaves = leaves; > public int numberOfLeaves() < return leaves; > > public class Banana extends Fruit < public final double curve; public Banana(double curve) < this.curve = curve; > public double curve() < return curve; > >
You can define Test Data Builders for Apples and Bananas with Make It Easy as follows:
public class FruitMakers < public static final PropertyFruit,Double> ripeness = newProperty(); public static final PropertyApple, Integer> leaves = newProperty(); public static final PropertyBanana,Double> curve = newProperty(); public static final InstantiatorApple> Apple = new InstantiatorApple>() < @Override public Apple instantiate(PropertyLookupApple> lookup) < Apple apple = new Apple(lookup.valueOf(leaves, 2)); apple.ripen(lookup.valueOf(ripeness, 0.0)); return apple; > >; public static final InstantiatorBanana> Banana = new InstantiatorBanana>() < @Override public Banana instantiate(PropertyLookupBanana> lookup) < Banana banana = new Banana(lookup.valueOf(curve, 0.5)); banana.ripen(lookup.valueOf(ripeness, 0.0)); return banana; > >; >
MakerApple> appleWith2Leaves = an(Apple, with(2, leaves)); MakerApple> ripeApple = appleWith2Leaves.but(with(ripeness, 0.9)); MakerApple> unripeApple = appleWith2Leaves.but(with(ripeness, 0.125)); Apple apple1 = make(ripeApple); Apple apple2 = make(unripeApple); Banana defaultBanana = make(a(Banana)); Banana straightBanana = make(a(Banana, with(curve, 0.0))); Banana squishyBanana = make(a(Banana, with(ripeness, 1.0)));
In contrast, doing so in the style documented in Growing Object-Oriented Software, Guided by Tests would look like this:
public interface BuilderT> < T build(); > public class AppleBuilder implements BuilderApple> < private double ripeness = 0.0; private int leaves = 1; private AppleBuilder() <> public static AppleBuilder anApple() < return new AppleBuilder(); > public Apple build() < Apple apple = new Apple(leaves); apple.ripen(ripeness); return apple; > public AppleBuilder withRipeness(double ripeness)< this.ripeness = ripeness; return this; > public AppleBuilder withLeaves(int leaves) < this.leaves = leaves; return this; > public AppleBuilder but() < return new AppleBuilder() .withRipeness(ripeness) .withLeaves(leaves); > > public class BananaBuilder implements BuilderBanana> < private double ripeness = 0.0; private double curve = 0.5; private BananaBuilder() <> public static BananaBuilder aBanana() < return new BananaBuilder(); > public Banana build() < Banana apple = new Banana(curve); apple.ripen(ripeness); return apple; > public BananaBuilder withRipeness(double ripeness)< this.ripeness = ripeness; return this; > public BananaBuilder withCurve(double curve) < this.curve = curve; return this; > public BananaBuilder but() < return new BananaBuilder() .withRipeness(ripeness) .withCurve(curve); > >
AppleBuilder appleWith2Leaves = anApple().withLeaves(2); AppleBuilder ripeApple = appleWith2Leaves.but().withRipeness(0.9); AppleBuilder unripeApple = appleWith2Leaves.but().withRipeness(0.125); Apple apple1 = ripeApple.build(); Apple apple2 = unripeApple.build(); Banana defaultBanana = aBanana().build(); Banana straightBanana = aBanana().withCurve(0.0).build(); Banana squishyBanana = aBanana().withRipeness(1.0).build();
As you can see, with Make It Easy you have to write a lot less duplicated and boilerplate code. What duplication there is — in the declaration of anonymous Instantiator classes, for example — can be automatically inserted and refactored by modern IDEs. (You could also factor out calls to Fruit.ripen to a private helper method, but I left them duplicated for clarity.)
The full code for this example is in the Make It Easy repository.