JUnit
is one of available Java libraries we can use to test our application code. Let’s get started by building
simple project using Maven
.
Generating project with JUnit support
Go generate simple Java
artifact using Maven
, which can be done quite easily using command line.
mvn archetype:generate -DgroupId={GROUP_ID} -DartifactId={ARTIFACT_ID} -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Just execute upper line in your console, giving it some {GROUP_ID}
and {ARTIFACT_ID}
. Make sure you haven Maven
installed and on your path, so that you can run it as described. This is not related to JUnit
testing, just give
it any values, I will use com.mydomain
as {GROUP_ID}
, and junit-demo
as {ARTIFACT_ID}
. That should generate
new Java
project ready to demo JUnit
tests. You can import generated application into IDE of your choice.
To confirm we have all we need, open generated pom.xml
file in the root of your project and verify there’s dependency to
JUnit
library in there (I’m using 4.12 version which is the latest stable version of JUnit
at the time of writing this article).
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
Once we confirm there’s given dependency, we will proceed with some theory alongside example code. We will create
simple calculator class which can do some basic operations as adding, subtracting, multiplying, dividing numbers.
We want to create JUnit
tests to make sure the calculator class is working as expected and has production ready
quality.
Creating System Under Test (SUT) and Test case
JUnit
testing takes care of testing our application at low level, meaning we want to make sure our class methods are tested against
different conditions, and they provide expected results in such a cases. Our methods we want to test can, during application runtime,
receive different values for input parameters. We want to make sure our method is tested against these. Of course – not against all
possible values, since that’s not possible, but against some meaningful values, as well as some edge cases where our methods can receive,
let’s say null
as parameter, or some other value that could potentially crush our application. Let’s start writing com.mydomain.Calculator.java
and JUnit
tests for it, com.mydomain.CalculatorTest.java
. Test classes are also known as Test cases.
It’s a common practice to name test class after class we’re testing (sometimes referred to as System Under Test – SUT), by appending Test
suffix to it. In our case, our SUT
is Calculator.java
, so our test class should be CalculatorTest.java
. Also, common practice is that test
class should reside in same package as SUT
one (in our case, both are in com.mydomain
package, but test class is in src/test/java
tree, and
SUT is in src/main/java
. These are maven defaults which state that application code should go to X, whereas test code should be inside src/test
packages. Our Calculator.java
class should look like:
package com.mydomain;
public class Calculator {
public long add(long first, long second) {
return first + second;
}
public long subtract(long first, long second) {
return first - second;
}
public long multiply(long first, long second) {
return first * second;
}
public long divide(long first, long second) {
return first / second;
}
}
Without writing JUnit
tests for given piece of code, we can only HOPE our code will work as expected. We should be able to
exercise this piece of code before deploying it to production so that we can be confident code is working as expected.
Our application should have layout such as given below (target
directory is incomplete here, but that’s irrelevant):
├── pom.xml
├── src
│ ├── main
│ │ └── java
│ │ └── com
│ │ └── mydomain
│ │ ├── App.java
│ │ └── Calculator.java
│ └── test
│ └── java
│ └── com
│ └── mydomain
│ ├── AppTest.java
│ └── CalculatorTest.java
└── target
Let’s now write some JUnit
tests in order to exercise our application. Our CalculatorTest.java
class could look like:
package com.mydomain;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class CalculatorTest {
@Test
public void twoAndThreeIsFive() throws Exception {
final long result = new Calculator().add(2, 3);
assertThat(result, is(5L));
}
@Test
public void twoAndZeroIsTwo() throws Exception {
final long result = new Calculator().add(2, 0);
assertThat(result, is(2L));
}
@Test
public void twoAndMinusTwoIsZero() throws Exception {
final long result = new Calculator().add(2, -2);
assertThat(result, is(0L));
}
@Test
public void threeMinusTwoIsOne() throws Exception {
final long result = new Calculator().subtract(3, 2);
assertThat(result, is(1L));
}
@Test
public void threeMinusThreeIsZero() throws Exception {
final long result = new Calculator().subtract(3, 3);
assertThat(result, is(0L));
}
@Test
public void threeMinusMinusThreeIsSix() throws Exception {
final long result = new Calculator().subtract(3, -3);
assertThat(result, is(6L));
}
@Test
public void threeXThreeIsNine() throws Exception {
final long result = new Calculator().multiply(3, 3);
assertThat(result, is(9L));
}
@Test
public void threeXZeroIsZero() throws Exception {
final long result = new Calculator().multiply(3, 0);
assertThat(result, is(0L));
}
@Test
public void threeXMinusThreeIsMinusNine() throws Exception {
final long result = new Calculator().multiply(3, -3);
assertThat(result, is(-9L));
}
}
Idea is to exercise our public
methods present in Calculator.java
class. We should give it various inputs, and assert that
results are as expected. In order to create JUnit
test, we should just mark our method with @Test
JUnit
annotation
(org.junit.Test
). When we execute this set of tests we wrote, JUnit
will executed all methods marked with @Test
annotation.
In each test method we have two phases:
- Call
SUT
method for particular input (parameters) - Assert that result matches our expectancy (e.g. we assert that 3 x 3 should be 9)
Running tests and verifying results
The easy way to run all the tests is to package our application, using Maven
command in our command line:
mvn clean package
In console output, after our application code was compiled, we can see that JUnit
tests are executed:
## T E S T S
Running com.mydomain.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.013 sec - in com.mydomain.AppTest
Running com.mydomain.CalculatorTest
Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 sec - in com.mydomain.CalculatorTest
Results :
Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
We can see that there’s total of 9 tests being run in CalculatorTest.java
and that there were no failures (Failures: 0
in log output). If some tests were failing, we would be presented with an error message, and Maven
build would have
failed. Let’s change implementation of threeMinusMinusThreeIsSix
method to wrongly assert that 3 multiplied by -3 should be 2.
@Test
public void threeMinusMinusThreeIsSix() throws Exception {
final long result = new Calculator().subtract(3, -3);
assertThat(result, is(2L));
}
When repeating upper Maven
command to package app, we will get output:
## T E S T S
Running com.mydomain.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.013 sec - in com.mydomain.AppTest
Running com.mydomain.CalculatorTest
Tests run: 9, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.027 sec <<< FAILURE! - in com.mydomain.CalculatorTest
threeMinusMinusThreeIsSix(com.mydomain.CalculatorTest) Time elapsed: 0.005 sec <<< FAILURE!
java.lang.AssertionError:
Expected: is <2L>
but: was <6L>
....
Tests run: 10, Failures: 1, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE<
We clearly see that build failed, and what’s causing build to fail. It’s quite handy we can quickly execute tests and easily
see if something is failing and why. Test can fail if SUT
has issues, or JUnit
tests are not properly written (such as we did
above – just for demo purpose).
Bare in mind that JUnit
test should have quality standards same as application code, so make sure you apply all the good practices
you already apply to application code. Also, JUnit
tests should be independent of each other, and must be able to run in any order
given. They should also be quick to execute and easy to verify results.
Key takeaways
JUnit
is library for automated unit testing
Idea is to exercise the application code for various cases before application goes live, thus preventing issues that might happen during application runtime.
Each JUnit
test should be independent of other tests, and should be able to run fast, so that we can execute them frequently.
The next post will be related to extending JUnit
test using Mockito
library.
Hope you liked the post and feel free to download application sources at GitHub.
That was all for today! Hope you liked it!