Pages

Friday, August 31, 2012

Java Unit Testing with JUnit 4.x from Terminal


JUnit


In this tutorial I will guide you through the process of installing and writing a simple java unit test all from the terminal (command line). I'm not going to go through explaining why test driven development is the way to go for most serious programmers out there, but rather explain how tests are created and then performed. Hopefully, when you go through this tutorial you will finally understand the advantages of unit testing yourself if not already so!

This tutorial assumes that you have a basic text-editor installed. In my demonstrations, I will be using the famous Vim text-editor; however, you can use your preferred text-editor let it be NotePad or any other text-editor you find suitable to your needs. Also, it is assumed that you have the Java Compiler and JVM installed.


First, let us download the JUnit library/Jar which we will import later in our code to run tests. To do so visit http://www.junit.org and download the latest Jar directly from there. Alternatively, you can download the entire JUnit project (source files) from GitHub, and then compile them, this ensures that you get the latest updated JUnit by the creator Kent Beck himself. To do so you can fetch the latest project from GitHub using non other than Git. If you are unfamiliar with Git you can ignore the next paragraph and just download the Jar directly from the above website. Otherwise, follow along to fetch and build the latest JUnit project.

From the terminal/command-line execute the following:

$ git clone git://github.com/KentBeck/junit

This will clone a copy of the latest project to your local disk. Once the download is complete, you should cd into the junit/ directory and then ant dist. Executing the last command will start building and compiling the project using the ant tool. When the build is done cd into junit4.x directory to find all the Jar files you will need in your future test driven projects. Again if all this is not clear, you can just download the Jar from http://www.junit.org and use it directly without going through the hassle of building and compiling.

OK! Away from JUnit for a bit. Let's say we want to create a simple class called MyClass.java that does division of two numbers. This class has only one method which is called division. The division method takes two arguments division(double x, double y), and obviously does division as in x / y. The code in MyClass.java is as follows:

// the class to be tested
public class MyClass{
       public double division(double x, double y){
                return x / y;
       }
}

Now let's say we want to test this class. Particularly, unit test the division(double x, double y) method. The test case we want to verify is the division by zero case. We first create a test class called MyClassTest.java. This test class is located in the same disk location where MyClass.java is located (This is not necessary. It can be located anywhere). Note that the test class has the same name as the original class but with Test appended to its name; such as, MyClassTest.java which tests MyClass.java. This is a JUnit convention that must be followed to help JUnit map the classes using reflection. The code in MyClassTest.java is as follows:

import org.junit.*;
import junit.framework.JUnit4TestAdapter;

// the testing class
public class MyClassTest{
 private MyClass runner;

 @Before
 public void setUp(){
  runner = new MyClass();
 }

 @Test
 public void testZeroDivision(){
  Assert.assertEquals(0.0, runner.division(100.0, 0.0), 0.0);
 }

 @After
 public void tearDown(){
  runner = null;
 }

 public static junit.framework.Test suite(){
  return new JUnit4TestAdapter(MyClassTest.class);
 }
}

Firstly, we import org.junit.* that will do the parsing of JUnit annotations, and also import junit.framework.JUnit4TestAdapter which will invoke the actual testing. Note that these imports are specific to JUnit 4.x and not JUnit 3.x or any other earlier versions to that matter. Because we want to test the MyClass object we declare a private member of that type and is called runner. 

Secondly, we introduce JUnit annotations; such as, @Test, @Before and @After. @Test is used before the test method to tell JUnit that this is a testing function or testing fixture in JUnit terms. @Before and  @After are used with JUnit specific methods setUp() and tearDown() respectively. These annotations are very important when you have multiple test methods that you want to test altogether. @Before tells setUp() to initialise the MyClass object runner before every test method and @After tells tearDown() to nullify that same object once the testing method is done. So if we have another testing method, @Before and @After will make sure to initialise and cleanup any objects you are testing before going through the following test case.

Thirdly, in the testing fixture/method testZeroDivision() we use another JUnit specific method which is Assert.assertEquals(expected, actual, delta). The assert mechanism is the most famous mechanism of verification in the world of JUnit testing. There are many assert methods; such as, assertEquals(), assertTrue(), assertNull() and so on, and all of them are overloaded for most of the basic Java datatypes. OK! back to assertEquals(expected, actual, delta). What we want is to have zero as a result whenever we divide by zero. Theoretically, any division by zero yields infinity, but computers are finite and have no concept of infinity; thus, we have to account for this case by forcing the division by zero to yield zero. So 100 / 0 = 0 . As the expected argument in our division method we insert 0.0 because this is the result we want. For the actual argument we insert the method we want to test and that is runner.division(double x, double y). Delta is the error or uncertainty threshold that assertEquals would accept. Because we are comparing two floating points this delta can be convenient sometimes; however, in our test case we want delta to be 0.0, that is no difference between the expected result and the actual result.

Finally, note that we have a public static junit.framework.Test suite() method. This static method will help in executing this test class whenever we use the java command in the terminal/command-line. This method returns an instance of the JUnit4TestAdapter which takes as argument to its constructor the name of the test class we want to run and that is MyClassTest.class.

Now, to actually run this test we have to first compile both classes using the javac command and then execute the test class using the java command. Put in mind that we have to include the JUnit Jar and the user classes in the classpath during our compilation and execution. We assume that both files are under the same directory in the file system. cd into that directory and execute the following commands from any unix-like terminal to run the test:


$ javac MyClass.java
$ javac -cp ../JARs/JUnit/JUnit4.11/junit-4.11.jar:./ MyClassTest.java
$ java -cp ../JARs/JUnit/JUnit4.11/junit-4.11.jar:./ org.junit.runner.JUnitCore MyClassTest
$ JUnit version 4.11-SNAPSHOT
.E.
Time: 0.009
There was 1 failure:
1) testZeroDivision(MyClassTest)
java.lang.AssertionError: expected:<0.0> but was:<Infinity>
 at org.junit.Assert.fail(Assert.java:93)
 at org.junit.Assert.failNotEquals(Assert.java:828)
 at org.junit.Assert.assertEquals(Assert.java:560)
 at org.junit.Assert.assertEquals(Assert.java:664)
 at MyClassTest.testZeroDivision(MyClassTest.java:19)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:46)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:43)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
 at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
 at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30)
 at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:270)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:62)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:52)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:307)
 at junit.framework.JUnit4TestAdapter.run(JUnit4TestAdapter.java:39)
 at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:83)
 at org.junit.runners.Suite.runChild(Suite.java:129)
 at org.junit.runners.Suite.runChild(Suite.java:25)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:62)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:52)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:307)
 at org.junit.runner.JUnitCore.run(JUnitCore.java:152)
 at org.junit.runner.JUnitCore.run(JUnitCore.java:131)
 at org.junit.runner.JUnitCore.run(JUnitCore.java:112)
 at org.junit.runner.JUnitCore.runMain(JUnitCore.java:93)
 at org.junit.runner.JUnitCore.runMainAndExit(JUnitCore.java:48)
 at org.junit.runner.JUnitCore.main(JUnitCore.java:39)

FAILURES!!!
Tests run: 1,  Failures: 1


In the first line we simply compile the class to be tested. MyClass.java has no dependencies; thus, there is no need to include anything in the classpath during compilation. The second line we compile MyClassTest.java. As you remember we have JUnit imports in this test class and have to include the JUnit jar in the class path accordingly. We also declared a private member of type MyClass, so we have to account for that by also including MyClass.class in the classpath. We do this by using the -cp argument to specify the jars and classes (separated by colons) that we want to include during the compilation. Because MyClass.class exists in the current directory we use ./ to instruct the compiler to look for this class in the current folder. In the third line we finally run the test by including the same in the classpath and then using the JUnit command-line runner to run the test and that is org.junit.runner.JUnitCore and then the test class name.

The output of the test is shown in the last box, and apparently the test has failed. Highlighted in red is the reason why the test has failed expected:<0.0> but was:<Infinity> . This is actually good, because it demonstrate exactly what JUnit should do. Since we have not accounted for the zero division case in MyClass.division() method then the test must fail! To fix this we have to add a small piece of code to MyClass.division() method to account for the zero division case.


// the class to be tested
public class MyClass{
       public double division(double x, double y){
                // if division by zero case
                if(y == 0.0)
                       return 0.0;

                return x / y;
       }
}

Now that we accounted for this case let's run the test again. Following is the output of the second run of the test:


$ JUnit version 4.11-SNAPSHOT
..
Time: 0.007

OK (1 tests)

Voila! the test was successful, and resulted in no failures which theoretically means that our code in MyClass.java could be moved to production (assuming this is the only case which can result in division errors).

Most major IDEs such as Eclipse, IntelliJ and many others have integrated JUnit into their suite of tools. I hope that by this simple example you can get a glimpse of test driven development.



No comments :

Post a Comment