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.

Before static import
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.

After static import
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.

single statement
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.

multiple statements
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.

Valid8JExample.java
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.

Valid8JExample.java
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.

Table 1. Output of Example.java
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.

Index

References