Let’s Use JUnit

Although JUnit is being projected as a standard in
testing java code but still there are many Java developers who have not
used it. One reason is lack of practical tutorials
with examples. In this tutorial, I have attempted to present Junit
usage in practical way with actual examples. There is lot more that you
can do with JUnit (beyond this tutorial) but it will kick start you
well, I believe.

Okay, let?s proceed.

Let’s Use JUnit

First obvious question, why to use JUnit when we
can write a main() method to test individual methods of a class?

Answer to this is ?better management?. If you
write calls to individual methods in main() method, you will have to
write a lot of code to reach where you want to reach. Here?s a list of
points:

  1. If one method call fails, next method calls
    won?t be executed. You will have to work-around this.
  2. Generally, you will execute main() of a class
    only when you are creating the class (that?s a bad habit though). If
    you want to execute main() of every class, every time code is changed,
    you will have to provide support for this yourself.
  3. If you want the test results to be displayed in
    a GUI, you will have to write code for that GUI.
  4. If you want to log the results of tests in HTML
    format or text format, you will have to write additional code.
  5. Your classes will be cluttered with test code
    in main method.
  6. If you start working on a project created by
    some other team in your organization, you may see an entirely different
    approach for testing. That will increase your learning time and things
    won?t be standard.

 Above are some of the problems,
which will be taken care of automatically if you use Junit. Junit
provides a standard framework for writing your tests. It separates test
code from project code, hence keeps everything clean.

Let?s now move forward to learning its usage.

Using Junit

I will not go into details of
installing Junit. Nevertheless, to tell you in brief, Junit is free
software. Search google to go to Junit download page, download and
extract it. Put junit.jar in classpath. It?s that simple.

Now look at following java code:

 /*
* DirLister.java
 *
 * Created on May 26, 2005, 10:14 AM
*/
package javaegs.junit;

import java.io.File;
import java.io.IOException;

/**
 *
 * @author varunc
 */

public class DirLister {

String dirPath;

 /** Creates a new instance of DirLister */

 public DirLister(String dirPath) {

    this.dirPath = dirPath;

 }

 /**
 * @param args the command line arguments
 */

 public static void main(String[] args) {

 // TODO code application logic here

}

 public void createLogFile(String fileName) {
     String absoluteFileName =dirPath+File.separator+fileName; 

     File f = new File(absoluteFileName);   

     try
     { 

     f.createNewFile();  

     }
     catch(IOException ioex)

     { 

     System.out.println("Some exception occured in creating file");  

     }
 }

 public boolean exists(String fileName) {

     String absoluteFileName =dirPath+File.separator+fileName; 

     File f = new File(absoluteFileName);   

     if (f.exists()) 
     {
     return true;
     }

     else
     {

     return false; 

     }
 }

 public String[] getChildList() throws Exception {

     String[] children=null;  

     File f = new File(dirPath);  

     if (!f.exists())

     {

        throw new Exception(dirPath+"does not exists");
     }  

     else if (!f.isDirectory())
     {
        throw new Exception(dirPath+" isnot a directory");
     }

     else
     {
        children = f.list();
     } 

     return children;
 }

}

Java class
DirLister.java has following three
methods: 
Note – dirPath is a member variable that
represents directory in use

  1. createLogFile(String fileName) ? Creates
    a file in dirPath
  2. exists(String fileName) ? Checks the
    existence of a file within dirPath
  3. getChildList() ? Gets the list of files
    and directories within dirPath

 We want to test above three
methods with Junit.
For that, we will create another Java class that will have the test
code for these three methods. See the class below:

/*
* DirListerTest.java
* JUnit based test
*
* Created on May 26, 2005, 11:33 AM
*/

package javaegs.junit;

import java.io.File;
import java.io.IOException;
import junit.framework.*;



/**
*
* @author varunc
*/

public class DirListerTest extends TestCase {

DirLister dl;

/**
* Test of createLogFile method, of class javaegs.junit.DirLister.
*/

public void testCreateLogFile() {
dl = new DirLister("D:/temp/junittestdir");
dl.createLogFile("logFile.log");
assertTrue("File does not exist",dl.exists("logFile.log"));
}

/**
* Test of exists method, of class javaegs.junit.DirLister.
*/

public void testExists() {
dl = new DirLister("D:/temp/junittestdir");
assertTrue("File does not exist",dl.exists("logFile.log"));
}

/**
* Test of getChildList method, of class javaegs.junit.DirLister.
*/

public void testGetChildList() {

dl = new DirLister("D:/temp/junittestdir");
String[] files = null;

try
{
files = dl.getChildList();
}
catch(Exception ex)
{
fail("Exception occured"+ex);
ex.printStackTrace();
}
assertNotNull("Children can't be null",files);
assertTrue("No. of files can't be 0",files.length>0);

}

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
junit.textui.TestRunner.run(DirListerTest.class);
}
}

Some points to be noted about this test class:

a) DirListerTest
class imports classes in junit.framework package. junit.framework
package contains all common classes required to create junit test cases.

b) DirListerTest
class extends junit.framework.TestCase class. TestCase class further
extends junit.framework.Assert class which contains various assertXXX()
methods required to created test cases. Therefore to use Junit
framework, test class must extend TestCase class.

c) Corresponding
to each method to be tested there is a method in the test class namely:

1. testCreateLogFile()
2. testExists()
3. testGetChildList()

You can name them
anything but the starting word must be test.
One important point I would like to mention here
is that I created three different methods to demonstrate three
different and common scenarios while programming.

  • exists() method of DirLister returns a value.
    Our test case will demonstrate how to test such methods, which return a
    value.
  • createLogFile() returns nothing. It even
    catches the exception and ignores it (in one sense). Therefore, we
    won?t get the exception even if that method fails. Our test case will
    demonstrate how to go about such methods.
  • getChildList() returns a value as well as may
    throw exception. We will see how to handle this using Junit framework.

Let?s now look inside each of above methods

testExists()

public void testExists()

{ 

 dl = new
DirLister("D:/temp/junittestdir");

 assertTrue(dl.exists("logFile.log"));

}

First statement in it instantiates the DirLister
object and points it to D:/temp/junittestdir directory. Next statement
is what we need to focus on:

    assertTrue("File does
not exist", dl.exists("logFile.log"));

assertTrue() is a standard method provided by
Junit framework (there are many other assert methods also). It takes
two arguments ? a string message and a boolean condition.

If boolean condition is true, test passes and
string message is ignored. Otherwise test fails and string message is
displayed.

In this case, if dl.exists("logFile.log") passes,
test passes, otherwise test fails.

There is another variant of assertTrue() method
provided by Junit that takes only one argument ? the condition to
check. This is true for all assertXXX methods provided by Junit.

Now you may say, that was a boolean result. What
if a non-boolean value is returned? Okay, so Junit provides
alternatives. We can use assertEquals() as below:

    assertEquals("not
equal",dl.exists("logFile.log"), true);

Here first argument is the message that is
displayed in case test fails.

Second argument is the actual value. Normally we
will call the method to be tested here and whatever value that method
will return, will become the actual value.

Third argument is the value we expect. So here, we
expect it to be ?true?, hence we put true there.

There are various overloaded forms of
assertEquals() method that can handle every result (from byte to String
to Object).

testCreateLogFile()

If a method returns nothing and throws
no
exception, either it is not doing anything or is creating a side
effect. createLogFile() is one such method. To test such methods, we
will have to test the side effect they created. That?s what
testCreateLogFile() does.

public void testCreateLogFile() {

 dl = new
DirLister("D:/temp/junittestdir");

 dl.createLogFile("logFile.log");

 assertTrue("File does not
exist",dl.exists("logFile.log"));

 }

We expect that dl.createLogFile("logFile.log")
should create logFile.log in directory in use. Therefore, in next
statement we check the existence of such a file.

 assertTrue("File
does not
exist",dl.exists("logFile.log"));

If file will be there, test case will pass,
otherwise it will fail.

testGetChildList()

getChildList() method may throw an exception.
Therefore, we will have to call it in try block.

 public void testGetChildList() {

 dl = new
DirLister("D:/temp/junittestdir");

 String[]
files = null;

 try

 {

 files =
dl.getChildList();

 }

 catch(Exception
ex)

 {

 fail("Exception
occured"+ex);

 ex.printStackTrace();

 }

 assertNotNull("Children
can’t
be null",files);

 assertTrue("No.
of files can’t
be 0",files.length>0);

 }

 If we don?t expect getChildList()
to throw
exception for the directory value we provide, we can put in catch block
a call to fail() method (as we have done).

fail() is again a standard method provided by
Junit framework (belongs to junit.framework.Assert class). It fails a
test case, which means it will show in the output that test case was
failed and show the message we provided as argument to it. It has a
without argument variant also.

One more thing fail() will do is to exit the
method. So assert statements following catch block will not be executed
if fail() is called. That?s logical since you should handle one bug at
a time in unit testing.

Okay, so what if getChildList() succeeds. If we
know that directory we have provided has at least one file in it, then:

    assertNotNull("Children
can’t be null",files);

will be useful. It will check that ?files?
variable is not null. If this test fails, it will display the message
provided by us.

assertTrue("No. of
files can’t be 0",files.length>0); will further make it certain that
number of files is greater than 0.

 If we know the number of files
within the
directory we can do something like this:

assertEquals(?Not
equal?, files.length, n); where n is the number of files.

 That?s how we test different
scenarios.

 Now, how to execute above tests.

Executing test cases

See the main method code:

public static void main(String[] args) {

  junit.textui.TestRunner.run(DirListerTest.class); 

}

junit.textui.TestRunner.run() method takes test
class name as argument. Using reflection, this method finds all class
methods whose name starts with test. So it will find following 3
methods:

1. testCreateLogFile()

2. testExists()

3. testGetChildList()

It will execute each of the 3 methods
in
unpredictable sequence (hence test case methods should be independent
of each other) and give the result in console. Result will be something
like this:

.testCreateLogFile

.testExists

.testGetChildList

Time: 0.016

OK (3 tests)

In case you want to see the output in a GUI, you
just need to replace statement in main method with following:

   
junit.swingui.TestRunner.run(DirListerTest.class);

This will open up a nice swing based UI, which
will show a green progress bar to show the status (do you still think
of writing tests without JUnit).

That?s not all my friends.

Grouping test cases

You can create a test suite (group/collection) of
all test cases in a class. This suite can further contain suites of
other classes. Therefore, executing top-level suite will automatically
call test cases of contained suites. To create a suite of test cases of
DirListerTest class, create a suite() method in this class:

public static Test
suite() {

TestSuite suite = new
TestSuite(DirListerTest.class);

return suite;

}

To make it contain suite of another class (say
LogWriterTest), use suite.addTest(LogWriterTest.suite()); statement.
Now your suite method will look like this: 

public static Test
suite() {

TestSuite suite = new
TestSuite(DirListerTest.class);

suite.addTest(LogWriterTest.suite());

return suite;

}

That means you must have defined similar suite()
method in LogWriterTest.java to contain test cases defined in
LogWriterTest class.

To execute this parent suite, you will have to
change your main method as below:

public
static void main(String[] args) {

 //junit.textui.TestRunner.run(DirListerTest.class); 

  junit.textui.TestRunner.run(suite()); 

  } 

If you were using graphics view, then you need not
to change your method to call suite. Therefore, following main() method
will work to execute outer suite and containing suites

public
static void main(String[] args) {

 junit.swingui.TestRunner.run(DirListerTest.class);

 }

Organizing test cases

You may feel that for each Java class we will be
creating a Junit class. That is not correct actually. You can very well
see that Test Class is not dependent on the class whose methods it has
to test (it?s not extending it). Therefore, if you feel that for two or
more classes it is logical to create one test class, you can do that.
It?s up to you.

Test Case Life Cycle

As I said earlier, test cases within a suit can
execute in any sequence. Therefore, test cases should be independent of
each other. Nevertheless, if some test cases have some common
environment to use, Junit supports that also.

For e.g. you can see in DirListerTest.java that
each of the 3 test methods execute following statement:

 dl = new
DirLister("D:/temp/junittestdir");

You can put that statement in a common method

public void setUp() {

 dl = new
DirLister("D:/temp/junittestdir"); 

} 

And you know what ? you need not to call
setUp() method in each test method
. Junit will do that for you
before executing each test method. That means after finishing a test
method it will again call setUp() method.

Normally, you would like to clean-up changes done
by a test method before setUp() is called again. Junit provides
tearDown() method, which you can use for clean up. This method (also)
will be called by Junit framework after execution of a test case. In
our case, it may look like below:
 

 public void tearDown()  {

 dl = null; 

 }

Therefore, the lifecycle of test cases will be
like:

setUp

testMethod 1

tearDown

setUp

testMethod 2

tearDown

setUp

testMethod 3

tearDown?????.and so on
 

There is lot more to learn in Junit, like creating
HTML reports, integrating junit scripts with ANT, using Junit
extensions. I can?t cover all, but I guess this tutorial will help in
generating interest in you mind for Junit and will get you started.
 
Best Wishes.

Content Team

The IndicThreads Content Team posts news about the latest and greatest in software development as well as content from IndicThreads' conferences and events. Track us social media @IndicThreads. Stay tuned!

0 thoughts on “Let’s Use JUnit

  • September 12, 2006 at 11:43 pm
    Permalink

    good article and right things said about spring.
    The main differnce b/w spring and struts is that Spring is light weight .

  • July 18, 2006 at 10:14 pm
    Permalink

    This is a note of ‘big’ thank you for your article. It’s good for quick reading which helps to understand the fundamentals of JUnit.

  • June 1, 2005 at 11:52 am
    Permalink

    You always can search google for ‘junit extensions’. HttpJUnit is one extension available for testing web apps.

  • June 1, 2005 at 10:41 am
    Permalink

    junit is good for stand alone classes. For web apps and apps that integrate with other apps, its quite difficult to use. I know there’s Cactus, etc. meant for web apps, but their usage is not as simple and straightforward as JUnit and so their adoption I think will be low.

Leave a Reply