To create an own# DeepDive User Guide
This user guide describes the fluent API of DeepDive, it's basic assert methods, fine points about error messages and AssertionErrors and how to create own assertion objects.
- Introduction
- Fluent API
- Basic assertions
- Error messages
- AssertionErrors
- Own Actual implementations
- Examples
A unit test computes a value and asserts that the actual vaTo create an ownlues meets the expectation,
else the test fails, usually by throwing an AssertionError.
Test frameworks usually offer support to express assertions:
org.junit.Assert(JUnit 4),org.junit.jupiter.api.Assertions(JUnit 5),org.testng.Assert(TestNG)
all offer a basic set of static assert methods to test expectations on actual values, e.g. to test if an actual value equals the expected value. DeepDive offers an own version of basic assert methods to replace JUnit or TestNG assertions.
A fluent assertion API goes beyond static assert methods: Given an actual computed value such an API provides a type specific assertion object which allows you to test properties of the actual value. DeepDive offers a fluent assertion API like AssertJ or Google Truth but additionally allows you to dive deep, i.e. going back and forth between different assertion objects. This results in a small library and API while at the same time allows to express a lot of assertions.
As starting point import the expectThat methods from deepdive.ExpectThat
import static deepdive.ExpectThat.*;Now given actual values computed by the test:
String s = ...
Map<Integer,String> map = ...test your expectations on these values:
expectThat(s)
.startsWith("a")
.endsWith("z");
expectThat(map)
.containsKey(1)
.containsValue("p")
.size(4);These two calls of expectThat return a deepdive.actual.java.lang.StringActual and a deepdive.actual.java.util.MapActual
object which provide type specific assertion methods. Both are derived from deepdive.actual.Actual.
But given the factory methods in ExpectThat you will rarely need to create an Actual object by yourself.
Besides methods to test expectations on a value, Actual classes may provide additional methods which return
other Actual objects. This allows you to dive deep into subproperties of the starting actual value. We
recommend to use indentation to clarify when switching of Actual objects takes place:
Map<Integer,String> map = ...
expectThat(map) // returns a new MapActual to test the map value
.size() // returns a new IntegerActual for the Map size
.greater(5)
.less(10)
.back() // going back to the MapActual
.keySet() // returns a new SetActual for the keyset of the map
.contains() // returns a new ContainsActual to test elements of the keyset
.someOf(3, 5)
.noneOf(2, 4, 6)
.back() // going back to the SetActual
.back() // going back to the MapActual
.containsValue("a");Any assertion method in an Actual can be negated by a preceding call to not()
expectThat("abc")
.contains("b")
.not().contains("z");If an assertion method fails it will by default throw an AssertionError which immediately terminates the test method.
DeepDive allows you to bundle assertion calls and run them all even if some assertions fail.
At the end eventually an AssertionError containing information about all failed assertions is thrown.
String s = ...
expectThat(s) // returns a deepdive.actual.java.lang.StringActual
.all(actual -> actual // actual is the StringActual passed to the lambda
.startsWith("A") // may fail but will not throw an error
.length().greater(5)) // will be executed even if the previous assertion startsWith("A") fails
.contains("B"); // will not be executed if soft assertion all() fails org.junit.Assert, org.junit.jupiter.api.Assertions and org.testng.Assert provide a basic set of static assert methods to test expectations:
import static org.junit.Assert.assertEquals;
String actual = ...
assertEquals("abc", actual); // throws an AssertionError if actual does not equal the expected value "abc"If you test expectations using the DeepDive fluent API based on Actual classes you don't need any basic assertion methods.
But still basic assertions are usefull from time to time - and the Actual classes itself implement their assertions using
basic assertion methods.
DeepDive provides a basic assertions API similar to JUnit and TestNG which comes in three flavors with respect how to
include it in your code. To differentiate from JUnit and TestNG and to make
transition to DeepDive easier it uses expect instead of assert as prefix for the assertion methods:
Use deepdive.ExpectStatic as a replacement for the static methods defined in org.junit.Assert or org.testng.Assert:
import static deepdive.ExpectStatic.expectEqual;
String actual = ...
expectEqual("abc", actual);Implement deepdive.ExpectInterface to inherit basic assertion methods (e.g. in a Test base class).
This is convenient if you don't want to write the import statement for ExpectStatic in each class
which uses basic assertions:
import org.junit.Test;
import deepdive.ExpectInterface;
public abstract class TestBase implements ExpectInterface {
}
public class CalculatorTest extends TestBase {
@Test
public void test() {
String actual = ...
expectEqual("abc", actual); // inherited from ExpectInterface
}
}Derive from deepdive.ExpectProtected to inherit basic assertion methods with a protected modifier:
Here we want to use assertion methods within derived classes but not include them in their public API.
The prototypical use case for this flavor are Actual classes which offer type specific public assertion methods for their
actual value and internally can implement these methods using basic assertions inherited from ExpectProtected:
import deepdive.ExpectProtected;
public class Actual<T,BACK,IMPL> extends ExpectProtected {
...
/** Asserts that the actual value is equal to the expected value.*/
public IMPL equal(Object expected)
{
expectEqual(expected, valueOrNull()); // calls ExpectProtected.expectEqual
return self();
}
}Suppose an assertion of a unit test fails - can you find and fix the error using only the error message and the failure stacktrace? Or do you need to debug the test to diagnose the error?
Informative error messages are key to good unit tests, last but not least for their psychological impact: Does the test code (judged by its failure messages) feel obscure or clear to you? Therefore DeepDive error messages try to bring as much usefull information as possible into error messages.
Following the example of Google Truth error messages:
- messages can span multiple lines
- values are reported as
<title>: <value> - subsequent value lines are nicely aligned.
If arrays, sets, lists or maps are tested if they equal an expected value, the difference between actual and expected value is described in great detail.
Diving deep into Actual objects will provide the full context of the starting actual value down to the failing detail assertion.
Many basic assertion methods in DeepDive have an overloaded form with an additional context parameter (in JUnit it is called message)
which is included in the error message when the assertion fails.
The type of that context parameter is CharSequence. Of course you can pass a String object but additionally
any CharSequence will do. To avoid heavy construction of error contexts which are not used if the assertion succeeds
this allows for relative cheap, lazily built error contexts as demonstrated by deepdive.Context.
import deepdive.Context;
import static deepdive.ExpectStatic.*;
...
String[] s = ...
// use a lightweight context parameter which would resolve to "[2]"
expectEqual("a", s[2], Context.indexed(2)); DeepDive recognizes if opentest4j (also part of JUnit5) or JUnit
are in the classpath and if so uses their AssertionError implementations since they are recognized and supported by IDEs (e.g.
used by Eclipse to show the CompareActualWithExpected dialog):
org.opentest4j.AssertionFailedErrorfor assertion failuresorg.opentest4j.MultipleFailuresErrorfor multiple assertion failures in soft modeorg.junit.ComparisonFailureiforg.opentest4j.AssertionFailedErroris not available
Else DeepDive will just throw an java.lang.AssertionError.
If you want to use other Error classes you can implement and set an own deepdive.impl.ErrorFactory to achieve that.
DeepDive provides Actual implementations for core JDK classes, but you might want to develop
Actual implementations for classes in your code base, especially for classes with a lot
of test cases: In this case it really pays off to base tests on Actuals since you get
clean, readable test code.
To create an Actual implementation for an own class you can use deepdive.tool.ActualGenerator as a starting point. Just
- include your own code and the deepdive jar into the classpath,
- call
ActualGenerator, - pass as argument the fully qualified name of your class,
- copy or redirect the output to a new java source file for your new
Actualimplementation, - and finally tweak the generated class to your needs:
java -cp <classpath> deepdive.tool.ActualGenerator org.example.CalculationResult >CalculationResultActual.java
The Actual base class has three type parameters:
| Type Param | Description |
|---|---|
<T> |
the type of the tested actual value |
<BACK> |
the type of the owner object which constructed the Actual, usually Void for Actual roots |
<IMPL> |
the implementation class of the Actual as recursive type bound |
public class NumberActual<T extends Number,BACK,IMPL extends NumberActual<T,BACK,IMPL>> extends Actual<T,BACK,IMPL>
- we restrict
Tto be aNumber - we restrict
IMPLto extendNumberActual<T,BACK,IMPL>
public class StringActual<BACK,IMPL extends StringActual<BACK,IMPL>> extends Actual<String,BACK,IMPL>
- since
Stringis final we don't need a type parameterT - we allow for derived implementations of
StringActualand therefore declare aIMPLtype parameter.
public class ListActual<ELEM,T extends List<ELEM>,BACK,IMPL extends ListActual<ELEM,T,BACK,IMPL>> extends CollectionActual<ELEM,T,BACK,IMPL>
ListActualtestsjava.util.Listobjects.- we add an additional type parameter
ELEMfor the type of the List elements - somebody might be interested in testing Lists of a certain class therefore we declare
Tto extendsList<ELEM> - we allow for derived implementations of
ListActualand therefore declare aIMPLtype parameter.
public class ShoppingCartActual<BACK> extends Actual<ShoppingCart,BACK,ShoppingCartActual<BACK>>
- an extremely reduced example for an
Actualimplementation: - we don't have to deal with derived values of class
ShoppingCart, so there is no type parameterT - we don't have to deal with derived implementations from
ShoppingCartActual, so there is no type parameterIMPL - (we might even drop type parameter
BACKifShoppingCartActualis never constructed with aBACKobject, and usesVoidas parent parameter type).
The test directory contains two examples to showcase DeepDive usage:
The chess package contains classes which model Chess entities.
The subpackages test1, test2, test3 demonstrate three different test strategies:
test1only uses static assertion methods provided bydeepdive.ExpectStatic.test2improves ontest1by introducing helper assertion methods which make it easier to read thantest1.test3usesActualimplementations for some chess classes and provides an easy to read test using the fluent assertions.
The tolkien package
introduces some domain classes and deepdive tests in the test subpackage.