Installation
Have a following maven dependency in your pom.xml
.
<dependency>
<groupId>com.github.dakusui</groupId>
<artifactId>valid8j</artifactId>
<version>{valid8j-version}</version>
</dependency>
Visit oss.sonatype.org to figure out the most recent version of valid8j
.
Fluent Style
Following is the first example of the valid8j
library.
public class DbC {
public void aMethod(int a) {
assert Expectations.precondition(Expectations.that(a).satisfies()
.greaterThan(0)
.lessThan(100));
}
}
Remember, start from the Expectations
class, then let your IDE help you.
Then, do static import
to improve readability.
public class DbC {
public void aMethod(int a) {
assert precondition(that(a).satisfies()
.greaterThan(0)
.lessThan(100));
}
}
The key differences from the existing "fluent" test libraries such as AssertJ or Google Truth are:
-
Your entry point is
Expectations
class. -
The methods for evaluating condition (
precondition
) and the methods to create the condition are separated.
The second allows us to use the consistent syntax for different contexts such as DbC assertions, test assertions, argument checking, etc.
Example: Test assertions
To build a test assertion, you use Expectations.assertStatement
method, if you have only one variable to be checked.
public class TestExample {
@Test
public void testMethod() {
assertStatement(that(a).satisfies()
.greaterThan(0)
.lessThan(100));
}
}
This produces output like following:
Exception in thread "main" java.lang.AssertionError: value:<[100]> violated precondition:value (at[0] WHEN:(>=[0]&&<[100])) Mismatch>: [100]->allOf ->false -> transform:at[0] ->100 Mismatch>: 100 -> WHEN:allOf ->false -> >=[0] ->true Mismatch>:[0] -> <[100] ->false .Detail of failure [0] (expectation) --- <[100] --- .Detail of failure [0] (actual value) --- 100 --- at com.github.dakusui.valid8j.pcond.validator.ExceptionComposer$ForAssertion.exceptionPreconditionViolation(ExceptionComposer.java:205) at com.github.dakusui.valid8j.pcond.validator.Validator.lambda$checkPrecondition$7(Validator.java:321) ...
However, if you have multiple variables, you need to evaluate multiple statements at once.
If you do it by calling assertStatement
multiple times, you will need to look into the failure report, fix it, and run it.
If it works, it may be okay, however, if it fails at the next point, you will need to do the same again.
Even worse, the second fix may introduce another failure at a different position.
To avoid it, use assertAll
, which accepts multiple statements at once.
public class TestExample {
@Test
public void testMethod() {
assertAll(that(a).satisfies()
.greaterThan(0)
.lessThan(0),
that(s).satisfies()
.notNull()
.notEmpty());
}
}
Classic Style
valid8j
has another style of writing value checks, which is called "classic" and provides a basis of the aforementioned "fluent" style.
public class Valid8JExample {
@Test
public void testString() {
TestAssertions.assertThat(
"hello World",
Predicates.transform(Printables.function("toUpperCase", o -> Objects.toString(o).toUpperCase()))
.check(Predicates.containsString("HELLO")));
}
}
For readability’s sake, it’s recommended to extract a function and do static import
, where possible.
public class Valid8JExample {
@Test
public void testString() {
assertThat(
"hello World",
transform(toUpperCase()).check(containsString("HELLO")));
}
private static Function<String, String> toUpperCase() {
return function("toUpperCase", o -> Objects.toString(o).toUpperCase());
}
}
The programming style of valid8j
is to first transform a given value into better known type or form, which is more suitable for examining its validity.
In this example, a given string is first transformed into all upper cases so that it is equal to "HELLO" if we ignore cases.
This results in a following output.
Expectation | Actual |
---|---|
"Howdy, World"->transform:toUpperCase ->"HOWDY, WORLD" [0] "HOWDY, WORLD"->check:containsString[HELLO] ->true .Detail of failure [0] --- containsString[hello] --- |
"Howdy, World"->transform:toUpperCase ->"HOWDY, WORLD" [0] "HOWDY, WORLD"->check:containsString[HELLO] ->false .Detail of failure [0] --- HOWDY, WORLD --- |
You can see the violated condition and how it is violated in the stacktrace.
A philosophy behind valid8j
's approach is:
-
A human in the end checks the value on a screen as a text.
-
Either way, test can only report a presence of a bug, cannot ensure absence of it. If so, rather than trying to build "matcher", "subject", or "assertion" objects that can do a check that detects all the bugs for a given class, it will be more productive to decompose the check into various checks, each of which is concise and understandable.
References
-
[1] Wikipedia article on Design by Contract, Design by contract
-
[2] valid4j valid4j.org
-
[3] pcond dakusui.github.io/pcond
-
[4] Valid4j, valid4j.org
-
[5] PreconditionsExplained, PreconditionsExplained
-
[6] Hamcrest hamcrest.org
-
[7] Programming With Assertions Programming With Assertions
-
[8] Preconditions, Google Guava Preconditions class
-
[9] Validates, Apache Commons Validate class