JUnit is one of a family of unit testing frameworks collectively known as xUnit, and is the most commonly used test framework in the Java ecosystem.
Similar unit testing frameworks are available for many other languages, and these unit test frameworks provide a convenient way to build unit, component, integration and end to end tests without having to write supporting code and have now become the norm for code based (as opposed to tool based) testing.
Test Lifecycle
As with manual tests, automated tests have expected outputs, and almost always have prerequisites and preconditions in addition to the test steps themselves.
Annotations
At the heart of JUnit class files are the annotations that describe what each method is for, the most commonly being an individual test and then additional annotations for actions that run before or after each test, or every test.
There is a full list of JUnit 5 annotations here:
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
Assertions
Assertions are the verification steps that produce the pass or fail results of each test – for example, does an actual output match the expected value.
A full list of JUnit 5 assertion methods are listed here:
The anatomy of a test
Here’s part of a simple example test showing common annotations and two simple mathematical assertions:
@BeforeAll
public static void setUpAll() {
System.out.println("once at the start");
}
@BeforeEach
public void setUp() {
System.out.println("before every test");
}
@AfterEach
public void tearDown() {
System.out.println("after every test");
}
@AfterAll
public static void tearDownAll() {
System.out.println("once at the end");
}
@Test
public void test1division() {
System.out.println("@Test - divison");
double d = 1.0 / 8.0;
assertEquals(0.125, d, 0);
}
@Test()
public void test2divisionWithException() {
System.out.println("@Test - divisonbyzero");
Exception exception = assertThrows(ArithmeticException.class, () -> {
int i = 1 / 0;
});
}
It’s important to note that the order is not governed by location within the file, but by the annotations and for the test annotation, the order is deterministic but not related to the name. It is possible to specify an order for tests by adding additional directives as described here:
In addition to the tests classes themselves, there is additional configuration required to actually run the tests. Rather than repeat this here (to copy and paste), you can download a fully working example of a simple JUnit 5 (and JUnit 4) test project from:
https://github.com/ObjectiveTester/AllThingsTesting.com-examples
Running a test
The easiest way to run these tests is probably from an IDE – consult the documentation of your preferred development environment, or if you’re feeling adventurous install a Java Development Kit and Maven.
Regardless of how you actually run the test, the output of the tests will be the same and demonstrates the lifecycle of the test.
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running example.com.SampleTest
once at the start
before every test
@Test - divisonbyzero
after every test
before every test
@Test - divison
after every test
once at the end
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.03 s - in example.com.SampleTest
As you can see from the output, this demonstrates where the various annotations result inwhich outputs, and the counterintuitive order of test execution.
Next Steps
This is a very simplistic look at the basics of JUnit – as a learning exercise, I would encourage you to extend the sample with additional assertion types, purposefully implement broken tests, and experiment with annotations including @ParameterizedTest.
In future blog posts I will explore other testing frameworks to perform both API and GUI testing.