You're up and running!
Application Development Tutorial
JUnit Basics
JUnit is a simple framework to write repeatable tests. You may go to http://junit.org/ for additional information regarding JUnit.
In this tutorial we will learn how to create a simple test class that is used to test the methods of a Java class.
Prerequisite: Having a good understanding of Java programming is required to do this tutorial. Test
Copy Sample Codes from Git repository
- Open a terminal window and create the directory
junittemp
in the root directory. Go to the created directory.
>mkdir junittemp
>cd junittemp
- Clone the git repository
https://hub.jazz.net/git/pantolav/junit-basics
and go to the createdjunit-basics
directory.
>git clone https://hub.jazz.net/git/pantolav/junit-basics
>cd junit-basics
The junit-basics
directory has two subdirectories: src
and build
.
src
has two subdirectories: main
and test
.
src/main
contains the Java class src/main/java/net/tutorial/Math.java
which we will be tested later using JUnit. In addition, it contains the src/main/java/net/tutorial/Calculator.java
which is a sample Java application that uses Math.java
.
src/test
contains the Java class src/test/java/net/tutorial/MyTest.java
which is the test class that will be used to test Math.java
. In addition, it contains the src/test/java/net/tutorial/TestRunner.java
which is a Java application that will run the test.
build
has two subdirectories: classes
and libs
.
build/classes
is used to hold the the .class
files that will be created later when we compile our .java
files.
build/libs
is used to contain the JUnit libraries (i.e, .jar
files) that you will download later.
Download the JUnit libraries
Go to https://github.com/junit-team/junit/wiki/Download-and-Install.
Just in case the URL is broken. You may go to http://junit.org/ and find the download link.
Download the latest version of
junit.jar
andhamcrest-core.jar
and save them in the subdirectorybuild/libs
.
Examine the Java class to be tested
- Let's examine the sample class
Math.java
which we will test later with JUnit.
src/main/java/net/tutorial/Math.java
:
package net.tutorial;
public class Math{
//will be used in the multiply method to simulate that
//the multiply method is taking too long to execute
private void delay(){
try{
Thread.sleep(3000);//3000 msec. or 3 sec. delay
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
public int add(int a, int b){
//a-b is used instead of a+b to simulate
//a possible error in the source code
return a-b;
}
public int sub(int a, int b){
return a-b;
}
public int multiply(int a, int b){
//added delay to simulate that this method is
//taking too long to execute
delay();
return a*b;
}
}
Math.java
contains the methods we want to test: add
, sub
, and multiply
.
The add
method is intentionally made incorrect by using return a-b;
instead of return a+b;
to demonstrate errors that may be detected by JUnit.
The multiply
method contains the line delay();
to force the multiply
method to execute for more than 3 secs. This is useful when we demonstrate the concept of timeout in JUnit.
The sub
method is included to serve as a control. Since the implementation of sub
is correct, JUnit should not report any error involving sub
.
- The sample code above may be used by other Java classes to create an application. An example of this is
Calculator.java
.
src/main/java/net/tutorial/Calculator.java
:
package net.tutorial;
public class Calculator{
public static void main (String args[]){
Math m = new Math();
System.out.println("5 + 9 = " + m.add(5, 3));
System.out.println("8 - 2 = " + m.sub(8, 2));
System.out.println("4 x 7 = " + m.multiply(4, 7));
}
}
Calculator.java
represents a sample application that uses the Math.java
class we discussed above.
- Compile
Math.java
andCalculator.java
. > Make sure that you are in thejunit-basics
directory before issuing the command below.
>javac -d build/classes/main src/main/java/net/tutorial/*.java
- Run the
Calculator
application.bash >java -classpath build/classes/main net/tutorial/Calculator
Output:bash 5 + 9 = -4 8 - 2 = 6 4 x 7 = 28
As expected, the output 5 + 9 = -4
is wrong. In addition, it took approximately 3 secs. before the line 4 x 7 = 28
appeared.
Test the Java class
- Let's examine the code
MyTest.java
which will serve as the test class to test the methods ofMath.java
.
src/main/test/net/tutorial/MyTest.java
:
package net.tutorial;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
public class MyTest{
private Math m;
@Before
public void initializeMath(){
m = new Math();
}
@Test(timeout=1000)
public void addShouldReturnSum() {
assertEquals("3 + 7 should be 10", 10, m.add(3, 7));
}
@Test(timeout=1000)
public void subShouldReturnDifference() {
assertEquals("5 - 9 should be -4", -4, m.sub(5, 9));
}
@Test(timeout=1000)
public void multiplyShouldReturnProduct() {
assertEquals("8 * 4 should be 32", 32, m.multiply(8, 4));
}
}
Let's look at some code segments in MyTest.java
that are not typically found in a Java code.
First of all MyTest.java
contains a static import:
import static org.junit.Assert.assertEquals;
Static import allows us to access static items (e.g., static methods) in a class without specifying the name of the class. As an example, JUnit has a class Assert
that has a static method assertEquals
. If a non-static import (i.e., the typical way of importing a class) is used:
import org.junit.Assert;
we need to specify the Assert
class when calling the assertEquals
method:
Assert.assertEquals("3 + 7 should be 10", 10, m.add(3, 7));
Since MyTest.java
utilizes static import, we may omit the class Assert
:
assertEquals("3 + 7 should be 10", 10, m.add(3, 7));
This makes the code shorter especially if we plan to use the assertEquals
method several times.
Aside from a non-static import, MyTest.java
utilizes several JUnit annotations :
@Test(timeout=1000)
@Before
Before we discuss @Test(timeout=1000)
let's discuss @Test
first. Placing the annotation @Test
before a method specifies that a method is used as a test method. Therefore, MyTest.java
has three test methods: addShouldReturnSum
, subShouldReturnDifference
, and multiplyShouldReturnProduct
.
@Test(timeout=1000)
has the same effect as @Test
but performs an additional test by monitoring the execution time of a test method. If a test method executes beyond a specified timeout then it will produce an error. The timeout is in msec. This means @Test(timeout=1000)
will wait for 1000 msecs. or 1 sec. for a test method to complete. If after 1 sec. a test method has not finished executing, a timeout error is reported.
In MyTest.java
, the three methods ofMath.java
(i.e., add
, sub
, and multiply
) are tested with a timeout of 1 sec. Recall that we intentionally placed a 3 secs. delay in the multiply
method. We expect a timeout error reported when we run the test later.
@Before
indicates that a method needs to be executed before each test method is executed. In MyTest.java
we use @Before
in the following:
@Before
public void initializeMath(){
m = new Math();
}
Given this, the initializeMath
method gets executed before each of the three test methods get executed. In MyTest.java
, we may opt to omit the @Before
annotation as well as the initializeMath
method. However, we need to insert the instantiation of m
in each test method. An example is shown below:
@Test(timeout=1000)
public void addShouldReturnSum() {
m = new Math();
assertEquals("3 + 7 should be 10", 10, m.add(3, 7));
}
@Test(timeout=1000)
public void subShouldReturnDifference() {
m = new Math();
assertEquals("5 - 9 should be -4", -4, m.sub(5, 9));
}
@Test(timeout=1000)
public void multiplyShouldReturnProduct() {
m = new Math();
assertEquals("8 * 4 should be 32", 32, m.multiply(8, 4));
}
}
There are other JUnit annotations aside from @Test
, @Test(timeout=1000)
, and @Before
. You may check the webpage Unit Testing with JUnit - Tutorial for a discussion of other JUnit annotations.
It can be observed that the names of the test methods in MyTest.java
(i.e., addShouldReturnSum
, subShouldReturnDifference
, and multiplyShouldReturnProduct
) are relatively long. This is essential to make the report generated by JUnit more intuitive. When a test method encounters an error, the name of the test method is included in the report. Having very descriptive method names allows developers to debug the codes faster.
The last thing that is worth examining in MyTest.java
is the assertEquals
method. The assertEquals
method accepts the following parameters: message
, expected
, and actual
.
If expected
is not equal to actual
, the assertEquals
method throws an AssertException
that contains a message that is based on the message
parameter.
Aside from assertEquals
, there are other methods that can be used for testing. You may check the webpage Unit Testing with JUnit - Tutorial for additional methods that can be used for testing.
- Let's now see how the test class
MyTest.java
is executed.TestRunner.java
is an application that runs the test methods found inMyTest.java
.
src/test/java/net/tutorial/TestRunner.java
:
package net.tutorial;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
public class TestRunner{
public static void main(String args[]){
Result result = JUnitCore.runClasses(MyTest.class);
int errorCtr = 0;
for (Failure failure : result.getFailures()) {
errorCtr++;
System.out.println("Error #:"+ errorCtr);
System.out.println(failure.toString());
System.out.println();
}
if (errorCtr == 0)
System.out.println("Congratulations! There are no errors.");
}
}
Notice that TestRunner.java
never explicitly called the test methods addShouldReturnSum
, subShouldReturnDifference
, and multiplyShouldReturnProduct
found in MyTest.java
.
Instead, it simply calls the runClasses
method in JUnitCore
:
Result result = JUnitCore.runClasses(MyTest.class);
The runClasses
method has a parameter MyTest.class
. It is able to identify the test methods to execute in MyTest.java
because of the @Test
annotations. The result of all test methods (i.e., succeeded or failed) are saved in result
which is an instance of Result
.
Result
has a getFailures
method which returns a List
of Failure
.
- Compile
MyTest.java
andTestRunner.java
. > Make sure that you are in thejunit-basics
directory before issuing the command below.
>javac -classpath build/libs/*;build/classes/main -d build/classes/test src/test/java/net/tutorial/*.java
Notice that the classpath includes build/libs/*
. Recall that we downloaded and saved the two JUnit jar
files in this directory.
Run the
TestRunner
application. ```bashjava -classpath build/libs/;build/classes/main;build/classes/test net/tutorial/TestRunner ``` **Output:* ```bash
Error #:1 multiplyShouldReturnProduct(net.tutorial.MyTest): test timed out after 1000 millisecondsError #:2 addShouldReturnSum(net.tutorial.MyTest): 3 + 7 should be 10 expected:<10> but was:<-4>
As expected, the `multiplyShouldReturnProduct` test method resulted to an error since the `multiply` method of `Math.java` has a call to the `delay` method which produces a 3 sec. delay. This is way longer than the 1 sec. timeout that is indicated in the annotation in the test method (i.e., `@Test(timeout=1000)`).
In addition, the `addShouldReturnSum` test method also failed since we intentionally made the `sum` method of `Math.java` incorrect. Recall that we used `return a-b;` instead of `return a+b;` in the `sum` method of `Math.java`.
####End of Tutorial