This is the second part in the series about Unit Testing. It continues after Unit Testing, Why bother.
JUnit is a java-based unit testing framework. This means that the framework and the tests that are to be written in java, allowing developers to use the framework to write tests without having to learn a new language. It also means that the unit tests can be placed under the same source control system as the code base that it is testing. Java’s OO nature makes it ideal for writing unit tests. With a good design, most classes should be able to function in isolation. Each class should operate on their own data, allowing for unit tests to be written easily.
There are a lot of advantages to JUnit.
- Java-based unit testing framework
- Elegantly simple
- Easy to write unit tests
- Easy to manage unit tests
- Open source
- Mature Framework
- De facto java standard
- Maven integration
- Generic testing framework
JUnit is not the answer for all testing needs. Even though it can be customized or extended for a number of forms of testing, it still is not useful on its own for a number of circumstances. What JUnit lacks shouldn’t discourage any one from writing unit tests because it is always better to test something than nothing at all.
Listed below are a few areas where using JUnit alone will not be enough. To make it possible to test these areas as well, either additional frameworks or tooling can be added to JUnit, or a completely different tool-set can be used.
- GUI testing
- Limited reporting capabilities
- EJB/Servlet/JSP/OSGI
Key concepts in Junit
TestCase
The TestCase is the class where the unit test are defined. It contains the test methods as well as some utility methods like setup and teardown which are executed before and after each unit test.
Test
This is the actual unit test. Each Test method should follow the Unit testing ideals.
- Self-contained
- Repeatable
- Automatable
- Easy to write
- Easy to understand
Setup
The test case can have zero or more setup methods. These come in two variants. Each variant uses a different annotation.
@BeforeAll
Setup methods with the @BeforeAll annotation are executed once before the entire testcase is started. They can be used for initializing or starting stuff that should be available during all tests. An example for this is a database connection.
@BeforeEach
Setup methods with the @BeforeEach annotation are executed before each test. These can be used to set test related items in a default state. An example would be a script to set a database to a default state via the execution of an SQL script.
Tear-down
The test case can have zero or more tear-down methods. These come in two variants. Each variant uses a different annotation.
@AfterAll
Tear-down methods with the @AfterAll annotation are executed once after all test methods have finished. They can be used to clean up stuff which has been started in the @BeforeAll methods. An example is closing a database connection.
@AfterEach
T|ear-down methods with the @AfterEach annotation are executed after each test. These can be used to clear test related items. An example would be static values. These are best cleared in the tear-down methods since it is not guaranteed that any calls at the end of a test method are actually reached, more on why that is later.
While it is tempting to put test validation which is needed in each test method into the tear-down methods, this is usually a bad practice. When such a validation failed, the report will point to the tear-down method and not to the actual test method. This makes it very difficult to figure out which test is actually failing.
Assert
Asserts are the way JUnit validates the result of the unit test. They compare the expected outcome against the actual outcome. In case of a mismatch, they generate a message in the test report.
Failure vs. Error
A failure is a result of an assertion not passing. An error is when an exception is thrown from the test method. When writing unit tests, there is a distinct difference between failures and errors. The first validates that the logic of the method under test has failed. The second indicates that the unit test itself has failed. The error can also be caused when the method under test throws an exception that the unit test did not anticipate. This is also a fault in the unit test.
Writing unit tests with Junit
Writing a unit test with JUnit is very easy. JUnit is annotation driven. This means that every test method and utility method is indicated by a corresponding annotation.
Example
The example below can be found on Github.
@Test public void testValidatePersonInvalidFirstName() { // Given Person person = new Person(); person.setFirstName("Some#invalid*characters"); person.setLastName("ValidLastName"); // When boolean result = validator.validatePerson(person); // Then assertFalse(result); }
This example illustrates a very simple testcase. It creates a Person object and then validates that person object. The test here is that a first name with illegal characters is marked as invalid.
The method is annotated with the @Test annotation. This annotation tells JUnit that this is a test method. The actual method name is irrelevant, although it is a good idea to come up with some sort of convention. The convention used here is “test” where the method under test is “validatePerson” and the test condition is “InvalidFirstName”. While this convention can lead to long method names, it does give a clear understanding of what is being tested.
The test method also shows the “Given, When, Then” test strategy. When using this test strategy, the three parts of a test become very clear; preparation (Given), execution (When), validation (Then).
Tips & Tricks
- Write self-contained unit tests
- A single test should test a single thing
- Use the Given, When, Then test strategy
- Set start state before each test
- Reset state after each test
- Treat unit tests as production code
- Avoid adding code to the class under test which is only ever used by test cases
Links
More in-depth resources about JUnit can be found on the official JUnit website at https://junit.org.
The examples used in this tutorial can be found on Github.
This tutorial continuous in the next part of the series, Unit Testing, Mocking