Programming Models
valid8j
supports two programming models, which are "fluent style" and the other is "classic style".
In general, it is easier to write your code in "fluents style" because IDE helps are available.
But the "fluent style" is implemented on top of "classic style" and some features may be only available in the classic one.
In this section, we walk through both styles and discuss points that would matter in everyday programming.
Fluent Style
Following is an example code written in fluent style.
IntroductionExample
classpublic class IntroductionExample {
public String examplePublicPublicMethod(String name, int basePrice) {
// Use `Expectations.requireXyz` method to check values in production.
requireArguments(
that(name).satisfies()
.notNull(),
that(basePrice).satisfies()
.greaterThanOrEqualTo(0)
.lessThan(10_000));
return examplePrivateMethod(name, basePrice);
}
private String examplePrivateMethod(String name, int basePrice) {
// Use `assert` statement with`Expectations.` {`precondition`,`invariant`,`postcondition`} methods
// and their plural for Design by Contract programming.
assert preconditions(
// `value(var)` and `that(var)` are synonyms. Use the one you like.
// `toBe(var)` and `satisfies(var)` are synonyms. Use the one you like.
value(name).toBe()
.notNull(),
value(basePrice).toBe()
.greaterThanOrEqualTo(0)
.lessThan(10_000));
int price = (int) (basePrice * 1.08);
return String.format("%s:%s", name, price);
}
@TestMethodExpectation(FAILURE)
@Test
public void exampleMethod() {
String message = examplePublicPublicMethod("Kirin Ichiban", 100);
// Use `Expectations.assertAll` for test assertions.
assertAll(
that(message)
.substringAfter(":")
.parseInt()
.satisfies()
.equalTo(110),
that(message)
.satisfies()
.startingWith("Kirin Ichiban"));
}
}
In Transfomer interface, and Checker interface, which gives you choices automatically through your IDE, some methods that do the same things are available.
For instance, that() and toBe() do the same as then .
They are synonym methods defined to increase your code readability.
When synonym methods are available for your purpose, choose one that best improves readability in code.
|
Classic Style
The fluent style is implemented based on the printable predicate mechanism. You can also directly use it, and it’s called "classic style". With the style, you will have more control and sometimes more concise error messages because the fluent style needs to compose more complex predicate structure.
- Test Assertions
-
Use static methods in
com.github.dakusui.valid8j.classic.TestAssertions
class such asassertThat(T, Predicate<? super T> predicate)
. - Design by Contract
-
Use static methods in
com.github.dakusui.valid8j.classic.Assertions
class such asthat(T, Predicate<? super T> predicate)
. - Value Checking
-
Use static methods in
com.github.dakusui.valid8j.classic.Requires
,com.github.dakusui.valid8j.classic.Ensures
, and`com.github.dakusui.valid8j.classic.Validates
classes.
For creating predicates to give to those methods, you can refer to static methods in com.github.dakusui.valid8j.pcond.forms.Predicates
class.
To compose a "transform-and-check" structure, use Predicates#transform(Function)
method, which returns TransformingPredicate.Factory
instance.
The returned instance has check(Predicate)
method that returns a printable predicate.
There is another entry point class com.github.dakusui.valid8j.pcond.forms.Functions
.
This class has methods that return printable functions whose return value can be passed to Predicates#transform(Function)
.
In order to create your own printable functions and predicates, you can use function
and predicate
methods in com.github.dakusui.valid8j.pcond.forms.Printables
.
In case you want to directly make your own function printable when you call Predicates#transform
method, use its overloaded version: Predicates#transform(String, Function)
.
Working with Your Own Class
You may sometime want to check your own class’s behavior.
In valid8j
, there are a couple of ways to do so.
One is to create a custom transformer, another is to use ObjectTransformer
, which is designed for general objects.
In this section both of them will be discussed.
Using ObjectTransformer
ObjectTransformer
is a general transformer which is returned by that(Object)
method in Expectations
class.
Since AbstractObjectTransformer#invoke(String, Object…)
method is available from it, you play with your class without needing to implement your own transformer.
class Example {
public void assertAllSalutes() {
assert all(
that(new Salute())
.invoke("inJapanese")
.asString()
.length()
.then()
.greaterThan(0),
that(new Salute())
.invoke("inEnglish")
.asString()
.length()
.then()
.greaterThan(0));
}
}
To invoke a static method, you can use AbstractObjectTransformer#invokeStatic(Class,String,Object…)
method.
Custom Transformer Approach
ObjectTransformer
is a handy way, but it sacrifices type safety.
A way to implement your own transformer will be discussed here.
Creating a Custom Transformer
Suppose that we have a class Book
.
public static class Book {
private final String abstractText;
private final String title;
public Book(String title, String abstractText) {
this.abstractText = abstractText;
this.title = title;
}
public String title() {
return title;
}
public String abstractText() {
return abstractText;
}
@Override
public String toString() {
return "Book:[title:<" + title + ">, abstract:<" + abstractText + ">]";
}
}
To support the class in valid8j
, what you need to write is as simple as:
public static class BookTransformer extends CustomTransformer<BookTransformer, Book> {
public BookTransformer(Book rootValue) {
super(rootValue);
}
public StringTransformer<Book> title() {
return toString(Printables.function("title", Book::title));
}
public StringTransformer<Book> abstractText() {
return toString(Printables.function("abstractText", Book::abstractText));
}
}
Testing Your Class with a Custom Transformer
class BookTest {
@Test
public void givenBook_whenCheckTitleAndAbstract_thenTheyAreNotNullAndAppropriateLength() {
Book book = new Book(
"De Bello Gallico",
"Gallia est omnis divisa in partes tres, quarum unam incolunt Belgae, " +
"aliam Aquitani, tertiam qui ipsorum lingua Celtae, nostra Galli appellantur.");
assertAll(value(book, BookTransformer::new)
.satisfies(tx -> tx.title()
.satisfies(ty -> ty.toBe().notNull())
.satisfies(ty -> ty.parseInt().toBe().greaterThanOrEqualTo(10).lessThan(40)))
.satisfies(tx -> tx.abstractText()
.satisfies(ty -> ty.toBe().notNull())
.satisfies(ty -> ty.length().toBe().greaterThanOrEqualTo(200).lessThan(400))));
}
}
Reading a Failure Message
Following is a failure message when a precondition
check fails.
Exception in thread "main" java.lang.AssertionError: value:<[100]> violated precondition:value (at[0] WHEN:(>=[0]&&<[100])) Mismatch<: [100]->allOf ->true Mismatch>: [100]->allOf ->false -> transform:at[0] ->100 Mismatch<: 100 -> WHEN:allOf ->true Mismatch>: 100 -> WHEN:allOf ->false -> >=[0] ->true Mismatch<:[0] -> <[100] ->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) ...
The lines starting with Mismatch<:
are expectations and ones with Mismatch>:
are actually observed values.
Maybe we will not be printing lines with Mismatch<: in future for conciseness.
|
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