Junit Basics
JUnit is a simple framework to write repeatable tests.
In this tutorial you 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.
You are not required (but recommended) to do the Git Basics Tutorial.
- However, ensure that you have Git client installed in your machine.
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://github.com/pong-pantola/junit-basics.git
and go to the createdjunit-basics
directory.> git clone https://github.com/pong-pantola/junit-basics.git > cd junit-basics
The
junit-basics
directory has two subdirectories:src
andbuild
.junit-basics/ | |----src/ | | | |----main/java/net/tutorial/ | | | | | |----Math.java | | |----Calculator.java | | | |----test/java/net/tutorial | | | |----MyTest.java | |----TestRunner.java | |----build/ | |----classes/ | | | |----main/ | |----test/ | |----libs/
src
has two subdirectories:main
andtest
.src/main
contains the Java classsrc/main/java/net/tutorial/Math.java
which you will test later using JUnit. In addition, it contains thesrc/main/java/net/tutorial/Calculator.java
which is a sample Java application that usesMath.java
.src/test
contains the Java classsrc/test/java/net/tutorial/MyTest.java
which is the test class that will be used to testMath.java
. In addition, it contains thesrc/test/java/net/tutorial/TestRunner.java
which is a Java application that will run the test.build
has two subdirectories:classes
andlibs
.build/classes
is used to hold the the.class
files that will be created later when you compile your.java
files.build/libs
is used for 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 you will test later with JUnit.Source code of
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 you want to test:add
,sub
, andmultiply
.The
add
method is intentionally made incorrect by usingreturn a-b;
instead ofreturn a+b;
to demonstrate errors that may be detected by JUnit.The
multiply
method contains the linedelay();
to force themultiply
method to execute for more than 3 secs. This is useful when you demonstrate the concept of timeout in JUnit.The
sub
method is included to serve as a control. Since the implementation ofsub
is correct, JUnit should not report any error involvingsub
.Math.java
may be used by other Java classes to create an application. An example of this isCalculator.java
.Source code of
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 theMath.java
class.Compile
Math.java
andCalculator.java
.> javac -d build/classes/main src/main/java/net/tutorial/*.java
Make sure that you are in the
junit-basics
directory before issuing the command above.It is worth noting that the subdirectory
build/classes/main
already exists prior to compilation. This is essential since the-d build/classes/main
option in the command above means that the subdirectorybuild/classes/main
will be used as the base directory of the.class
files that will be created during compilation. If the subdirectory does not exist, the command above will produce an error.Run the
Calculator
application.> java -classpath build/classes/main net/tutorial/Calculator
Output:
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 line4 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
.Source code of
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 you 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 methodassertEquals
. If a non-static import (i.e., the typical way of importing a class) is used:import org.junit.Assert;
you need to specify the
Assert
class when calling theassertEquals
method:Assert.assertEquals("3 + 7 should be 10", 10, m.add(3, 7));
Since
MyTest.java
utilizes static import, you may omit the classAssert
:assertEquals("3 + 7 should be 10", 10, m.add(3, 7));
This makes the code shorter especially if you 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
, andmultiplyShouldReturnProduct
.@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
, andmultiply
) are tested with a timeout of 1 sec. Recall that we intentionally placed a 3 secs. delay in themultiply
method. We expect a timeout error reported when you run the test later.@Before
indicates that a method needs to be executed before each test method is executed. InMyTest.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. InMyTest.java
, you may opt to omit the@Before
annotation as well as theinitializeMath
method. However, you need to insert the instantiation ofm
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
, andmultiplyShouldReturnProduct
) 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 theassertEquals
method. TheassertEquals
method accepts the following parameters:message
,expected
, andactual
.If
expected
is not equal toactual
, theassertEquals
method throws anAssertException
that contains a message that is based on themessage
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
.Source code of
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 methodsaddShouldReturnSum
,subShouldReturnDifference
, andmultiplyShouldReturnProduct
found inMyTest.java
.Instead, it simply calls the
runClasses
method inJUnitCore
:Result result = JUnitCore.runClasses(MyTest.class);
The
runClasses
method has a parameterMyTest.class
. It is able to identify the test methods to execute inMyTest.java
because of the@Test
annotations. The result of all test methods (i.e., succeeded or failed) are saved inresult
which is an instance ofResult
.Result
has agetFailures
method which returns aList
ofFailure
.Compile
MyTest.java
andTestRunner.java
.Make sure that you are in the
junit-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 you downloaded and saved the two JUnitjar
files in this directory.Run the
TestRunner
application.> java -classpath build/libs/*;build/classes/main;build/classes/test net/tutorial/TestRunner
Output:
Error #:1 multiplyShouldReturnProduct(net.tutorial.MyTest): test timed out after 1000 milliseconds Error #: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 themultiply
method ofMath.java
has a call to thedelay
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 thesum
method ofMath.java
incorrect. Recall that we usedreturn a-b;
instead ofreturn a+b;
in thesum
method ofMath.java
.
End of Tutorial
Go back to the List of Tutorials.