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
junittempin the root directory. Go to the created directory.> mkdir junittemp > cd junittempClone the git repository
https://github.com/pong-pantola/junit-basics.gitand go to the createdjunit-basicsdirectory.> git clone https://github.com/pong-pantola/junit-basics.git > cd junit-basicsThe
junit-basicsdirectory has two subdirectories:srcandbuild.junit-basics/ | |----src/ | | | |----main/java/net/tutorial/ | | | | | |----Math.java | | |----Calculator.java | | | |----test/java/net/tutorial | | | |----MyTest.java | |----TestRunner.java | |----build/ | |----classes/ | | | |----main/ | |----test/ | |----libs/srchas two subdirectories:mainandtest.src/maincontains the Java classsrc/main/java/net/tutorial/Math.javawhich you will test later using JUnit. In addition, it contains thesrc/main/java/net/tutorial/Calculator.javawhich is a sample Java application that usesMath.java.src/testcontains the Java classsrc/test/java/net/tutorial/MyTest.javawhich is the test class that will be used to testMath.java. In addition, it contains thesrc/test/java/net/tutorial/TestRunner.javawhich is a Java application that will run the test.buildhas two subdirectories:classesandlibs.build/classesis used to hold the the.classfiles that will be created later when you compile your.javafiles.build/libsis used for the JUnit libraries (i.e,.jarfiles) 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.jarandhamcrest-core.jarand save them in the subdirectorybuild/libs.
Examine the Java class to be tested
Let's examine the sample class
Math.javawhich 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.javacontains the methods you want to test:add,sub, andmultiply.The
addmethod is intentionally made incorrect by usingreturn a-b;instead ofreturn a+b;to demonstrate errors that may be detected by JUnit.The
multiplymethod contains the linedelay();to force themultiplymethod to execute for more than 3 secs. This is useful when you demonstrate the concept of timeout in JUnit.The
submethod is included to serve as a control. Since the implementation ofsubis correct, JUnit should not report any error involvingsub.Math.javamay 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.javarepresents a sample application that uses theMath.javaclass.Compile
Math.javaandCalculator.java.> javac -d build/classes/main src/main/java/net/tutorial/*.javaMake sure that you are in the
junit-basicsdirectory before issuing the command above.It is worth noting that the subdirectory
build/classes/mainalready exists prior to compilation. This is essential since the-d build/classes/mainoption in the command above means that the subdirectorybuild/classes/mainwill be used as the base directory of the.classfiles that will be created during compilation. If the subdirectory does not exist, the command above will produce an error.Run the
Calculatorapplication.> java -classpath build/classes/main net/tutorial/CalculatorOutput:
5 + 9 = -4 8 - 2 = 6 4 x 7 = 28As expected, the output
5 + 9 = -4is wrong. In addition, it took approximately 3 secs. before the line4 x 7 = 28appeared.
Test the Java class
Let's examine the code
MyTest.javawhich 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.javathat are not typically found in a Java code.First of all
MyTest.javacontains 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
Assertthat 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
Assertclass when calling theassertEqualsmethod:Assert.assertEquals("3 + 7 should be 10", 10, m.add(3, 7));Since
MyTest.javautilizes 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
assertEqualsmethod several times.Aside from a non-static import,
MyTest.javautilizes several JUnit annotations :@Test(timeout=1000) @BeforeBefore we discuss
@Test(timeout=1000)let's discuss@Testfirst. Placing the annotation@Testbefore a method specifies that a method is used as a test method. Therefore,MyTest.javahas three test methods:addShouldReturnSum,subShouldReturnDifference, andmultiplyShouldReturnProduct.@Test(timeout=1000)has the same effect as@Testbut 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 themultiplymethod. We expect a timeout error reported when you run the test later.@Beforeindicates that a method needs to be executed before each test method is executed. InMyTest.javawe use@Beforein the following:@Before public void initializeMath(){ m = new Math(); }Given this, the
initializeMathmethod gets executed before each of the three test methods get executed. InMyTest.java, you may opt to omit the@Beforeannotation as well as theinitializeMathmethod. However, you need to insert the instantiation ofmin 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.javais theassertEqualsmethod. TheassertEqualsmethod accepts the following parameters:message,expected, andactual.If
expectedis not equal toactual, theassertEqualsmethod throws anAssertExceptionthat contains a message that is based on themessageparameter.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.javais executed.TestRunner.javais 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.javanever explicitly called the test methodsaddShouldReturnSum,subShouldReturnDifference, andmultiplyShouldReturnProductfound inMyTest.java.Instead, it simply calls the
runClassesmethod inJUnitCore:Result result = JUnitCore.runClasses(MyTest.class);The
runClassesmethod has a parameterMyTest.class. It is able to identify the test methods to execute inMyTest.javabecause of the@Testannotations. The result of all test methods (i.e., succeeded or failed) are saved inresultwhich is an instance ofResult.Resulthas agetFailuresmethod which returns aListofFailure.Compile
MyTest.javaandTestRunner.java.Make sure that you are in the
junit-basicsdirectory before issuing the command below.> javac -classpath build/libs/*;build/classes/main -d build/classes/test src/test/java/net/tutorial/*.javaNotice that the classpath includes
build/libs/*. Recall that you downloaded and saved the two JUnitjarfiles in this directory.Run the
TestRunnerapplication.> java -classpath build/libs/*;build/classes/main;build/classes/test net/tutorial/TestRunnerOutput:
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
multiplyShouldReturnProducttest method resulted to an error since themultiplymethod ofMath.javahas a call to thedelaymethod 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
addShouldReturnSumtest method also failed since we intentionally made thesummethod ofMath.javaincorrect. Recall that we usedreturn a-b;instead ofreturn a+b;in thesummethod ofMath.java.
End of Tutorial
Go back to the List of Tutorials.
