BPUnit (short for Boiler Plate Unit) is a set of utilities to help alleviate the pain of boiler-plating your JUnit tests.
The opposite of making a mistake is testing.
Alleh, N. (2004).
All too often you encounter a situation where some piece of the code was not tested because "it's too simple to break", or "it's not worth the effort". More often than not, these are the places that contain the bugs.
BPUnit attempts to fill this gap by offering a set of utilities to make testing even these boring parts of the code as pain free as possible.
The POJOAsserter
class does exactly what its name says - it asserts a
POJO. Well, its properties, at least.
POJOAsserters
aren't created directly, they are built with a
POJOAsserterBuilder
, unsurprisingly. The basic flow, detailed below,
will iterate over all of the object's properties, set
each one
with a random value, get
it back, and compare the two. If any of
these values don't match, the test will fail:
new POJOAsserterBuilder().forPOJO(myObject).build().assertProperties();
POJOAsserterBuilder
is a fluent API to create POJOAsserter
instances. It strives to assume as little as possible about the
assertion behavior you'd want by allowing you to configure the
POJOAsserter
with a series of withXYZ
methods. Currently, there are
three behaviors that can be configured:
- Handling a situation where your POJO class has a setter method such
as
setSomething(SomeClass)
, but does not have a corresponding getter (getSomething()
, in this example). This behavior can be influced by using thewithNoGetterBehavior(Behavior)
method. If not explicitly configured, the default behavior is to log such incidents, and continue the test. - Handling a situation where you cannot test a getter/setter pair
(e.g., the getter throws an exception).
This behavior can be influced by using the
withPropertyTestFailureBehavior(Behavior)
method. If not explicitly configured, the default behavior is to fail the test. - Handling a situation where your
Radnom
generator can't generate a random value for one of your POJO's properties (e.g., it doesn't have the right method). If not explicitly configured, the default behavior is to log such incidents, and continue the test.
E.g.:
new POJOAsserterBuilder().forPOJO(myObject)
.withNoGetterBahvior(new FailureBehavior())
.build()
.assertProperties();
BPUnit provides several simple built-in behaviors to use with
POJOAsserterBuilder
:
LoggingBehavior
- logs a message and continues the assertion flow.FailingBehavior
- fails the assertion flow.ThrowingBehavior
- throws anIllegalArguemntException
While java.util.Random
is a neat little randomizer, it has several drawbacks. First and
foremost, it can only randomize a small subset of the JDK's types,
whereas the classes you'd want to test usually have properties from your
own classes.
POJOAssertBuilder#withRadnom(Random)
allows you extend
java.util.Random
and provide methods to randomize your own classes. A
randomizer for MyClass
is a method with the following properties:
- It's public.
- Its return type is
MyClass
. - Its called
nextMyClass
. - It doesn't have any arguments.
These new extensions of Random
can be used with a POJOAsserter
by
calling the withRandom
method when building it. If not explicitly
specified, the SeedableRandom
class described below is used.
new POJOAsserterBuilder().forPOJO(myObject).withRandom(new MyRandom()).build().assertProperties();
A test that cannot be reproduced probably has a limited value, and at
first glance using random values in a test does not lend to that notion.
Fortunately, since Java can only really guarantee a pseudo-random
number, even these so called "random" numbers can be generated in a
predictable way by controlling the seed. This is where SeedableRandom
comes in to play. It's seed can be specified from the code by using
SeedableRandom(long)
or externally by using the default constructor
SeedableRandom()
and setting the BPUNIT.SEED
environment variable.
In any event, the seed will be logged so that it can be re-injected in
case a test fails.
The general philosophy behind BPUnit is that it should be as lightweight
and as non-intrusive as possible. Under this philosophy, BPUnit should
not depend on any other external libraries (except for
JUnit, of course). This is why SeedableRandom
re-implements several methods present in
Apache Commons Lang,
and why AssertUtils
does not use Apache Commons
BeanUtils or
anything of the sort. However, it's pretty hard to keep the dependency
list completely empty and some were introduced:
-
Obviously, as a set of utilities to helper for JUnit, BPUnit must depend on it. BPUnit depends on JUnit Jupiter 5.3.2, the latest JUnit release. JUnit does not break backwards compatibility between minor versions, and although BPUnit does not guarantee it, it strive to use only the the most trivial APIs JUnit provides in order to be backwards compatible with older JUnit 5.x releases.
-
SLF4J is used to hide the logging implementation details. This is done to allow other projects using BPUnit to easily integrate its logging with theirs by dropping in the appropriate adapter and supplying it with the proper configuration. BPUnit's tests run using the simple implementation in order to eliminate the need for an additional 3rd party jar but the "production" code is not aware of this fact, and, as noted, it can easily be replaced. BPUnit uses the
slf4j-api
andslf4j-simple
libraries from the 1.7.21 release in order to incorporate bug fixes and implementation improvements. However, note that it only uses trivial syntax, and would be binary compatible with any slf4j binary from the 1.7.x generation, if not earlier.
BPUnit is licensed under the LGPL-3 license. See LICENSE.md for additional details.