Unit Testing: Written by Patrick Kua Oracle Australian Development Centre Oracle Corporation
Unit Testing: Written by Patrick Kua Oracle Australian Development Centre Oracle Corporation
9 Document References......................................................................................36
1 OVERVIEW
1.1 Document Purpose
This document was created to describe:
What is testing?
What is unit testing?
The benefits of unit testing.
The JUnit framework for testing of java programs.
Best practices for JUnit.
How to unit test BC4J components.
Unit testing resources.
1.3 References
Note that most of the information in this document is widely available and that this document is intended
to be a convenient place where all of this information is collated and presented. References to the various
sources from which the information contained in this document has been based upon are given where
applicable.
This document was heavily based on the Unit Testing Java Programs document composed by Brad Long
and as of the writing of this document is available on Oracle Files Online in the “Healthcare
Development – Australia” workspace.
Page 1 of 42
2 TESTING
2.1 Introduction
Anyone who has been involved in any part of any software engineering project will have encountered
some form of testing. It is a fundamental process that has traditionally been given the least priority or has
been completed with the least amount of effort. There are countless textbooks, resources and websites
devoted to this topic, but the purpose of testing generally remains the same. A generic definition for the
term ‘test’ as provided by Dictionary.com is “A procedure for critical evaluation; a means of determining
the presence, quality, or truth of something; a trial”.
Testing can therefore be considered an acceptance mechanism that demonstrates a piece of software is
operating correctly as specified by its functional requirements. The majority of testing has generally been
left late in the software engineering process, as outlined in the Waterfall Model1. Modern software
engineering approaches such as Extreme Programming2 take the opposite view by having a test-first
strategy in which the amount of code developed is driven by the fulfilment of test cases that have been
written first.
Regardless of the software engineering methodology that is adopted by your project, it is vital that some
form of testing is completed.
... ...
... Functional
Testing
...
Integration System
Testing Testing
Acceptance
... Testing ...
... ...
Figure 1: Comprehensive Unit Testing is a better foundation for other forms of testing
1
The Waterfall Model is a model used to described the traditional approach to software engineer. -
Managing the Development of Large Software Systems, IEEE 1970, Royce, Winston W
2
Extreme Programming Website: http://www.extremeprogramming.org
Page 2 of 42
In contrast, unit testing focuses on validating that the smallest unit in a system fulfils the contract that is
defined by its API and documentation. Once each of these units have been tested and the tests are all
passing, other forms of testing can be applied to validate that the software application meets its other
requirements. In a diagrammatic form, unit testing can be considered the basis for many other forms of
testing, as pictured in Figure 1.
From a practioner’s perspective, unit testing java programs means writing a number of tests that validates
the methods exposed by a class to other classes are operating as expected. The methods exposed by a
class to other classes include public, protected and package-friendly methods.
3
A JUnit Primer - http://www.clarkware.com/articles/JUnitPrimer.html#usage
Page 3 of 42
2.4 Why Has Unit Testing Been So Neglected
There are many reasons why unit testing may not have been adopted for your software engineering
project. These include:
Benefits not fully recognised or evaluated – The benefits mentioned in the previous section
may not have been identified and valued by members of the software engineering team. As a
result, their software project misses out on the value-added proposition that unit testing offers.
No Time Was Allocated For Unit Testing – Writing unit tests takes time for a developer to
write. Thus it can be difficult to meet deadlines if time is not budgeted to include the time it
takes to write unit tests as well as the code that meets the requirements.
Difficult to Write – Before the JUnit4 framework, there was no easy way for unit tests to be
written in a manner that was easy, giving developers less of an incentive to write unit tests.
Java’s object-oriented nature also allows most classes to be tested in isolation, and Java’s
incremental compilation to class files makes changes to source and unit tests quick and easy to
run.
Difficult to Manage – The ad-hoc nature of which the unit tests had to be written prior to JUnit
meant that projects had to develop their own standard for writing and managing their unit tests.
Proprietary frameworks add to the time it takes for new developers to adjust to the project, while
the lack of any unit testing framework makes it even more difficult for other developers to share
the same code base project-wide.
Poor Development Attitude - Some developers assume that their code will operate exactly the
way they think the first time around. They might write a simple test harness or run through a few
informal tests to validate their code operates, but rarely are these committed to any formal source
control system. As a result, there is no guarantee that when changes are made to that code in the
future that the intended behaviour does not change.
4
JUnit – A unit-testing framework developed for testing Java classes. http://www.junit.org
Page 4 of 42
3 JUNIT
3.1 Introduction to JUnit
JUnit is a testing framework that was developed for the Java programming language aimed at providing
an easy way of developing unit tests. It is available from the JUnit website http://www.junit.org. One of
the most favoured introductions to this framework is the JUnit Primer5 article written by Mike Clark of
Clarkware Consulting, Inc. An excellent description of JUnit’s design is provided by the original authors
Gamma and Beck in the Cook’s Tour6 available from the JUnit website.
5
A JUnit Primer - http://www.clarkware.com/articles/JUnitPrimer.html
6
Cook’s Tour of JUnit - http://junit.sourceforge.net/doc/cookstour/cookstour.htm
Page 5 of 42
Easy Integration With Ant – The Ant7 tool has been used heavily in healthcare to control the
development process in a platform independent matter. JUnit is one of the standard optional
tasks, making it even easier to execute the unit tests for different areas of the application.
JUnit is a generic testing framework – Even though its original purpose was to facilitate unit
testing, the stability and maturity of the framework allows JUnit to be leveraged for all other
types of testing. Some examples include functional or system testing, integration testing and/or
even performance testing.
7
Ant, an open source build and task management system driven by XML - http://ant.apache.org/
Page 6 of 42
Cactus (http://jakarta.apache.org/cactus) - As described on the JUnit website, Cactus is a simple
test framework for unit testing server-side java code (Servlets, EJBs, Tag Libs, etc). The intent
of Cactus is to lower the cost of writing tests for server-side code. It uses JUnit and extends it.
Cactus has been developed with the idea of automatic testing in mind and it provides a packaged
and simple mechanism based on Ant to automate server-side testing. An alternative but
complementary approach not covered by Cactus is to use Mock Objects.
HttpUnit (http://httpunit.sourceforge.net) - Provides an easy mechanism for simulating HTTP
requests/responses in order to test code that communications over HTTP.
MockObjects (http://www.mockobjects.com) - A website devoted to building a framework for
testing code with the use of mock objects. Mock objects can be used to replace complex
resources in order to separate a resource dependency that may be encountered when testing
complex systems.
JMTUnit (http://www.michaelmoser.org/jmtunit) – A framework developed to help test
multithreaded applications.
3.4.1 TestCase
The base class for all test classes, the TestCase provides a container for defining all unit tests. It
implements the Assert interface providing an explicit way of stating the expected results of a unit tests.
Some of the key methods in this class include:
assertTrue – A method that provides a number of ways of ensuring a particular expression
evaluates to true.
assertEquals – A method that provide a number of ways of ensuring that two objects are
equivalent.
assertNull – A method to validate that a particular object is null.
assertNotNull – A method to validate that a particular object is not null.
fail – A method to force a failure when it is reached. This method is intended for use to flag
branches in code that should not have been reached. A typical use for this statement applies
when an expected exception has not been thrown.
All of the assertions above have overloaded methods that allow developers to specify a meaningful
message that will be displayed if the assertion fails.
Page 7 of 42
The TestCase class also provides two methods for maintaining the environment required for each of the
test cases defined. These methods are:
setUp() – The code defined in this block of code is called before each test is run to provide
for hooks that set up the environment or initialise objects for every test. This method is only
called exactly once before every test method.
tearDown() – The code that is defined in this block of code is called after each test is run to
provide for hooks that clean up the test environment. This method is only called exactly once
after every test method.
3.4.2 TestSuite
The TestSuite class does not provide much functionality but what it does provide is a simple
mechanism for managing tests. Each test suite enables individual tests, or a suite of other tests to be
embedded in itself. The recursive manner in which test suites can be embedded in other test suits allows
for a hierarchy of unit tests to be defined easily.
3.4.3 TestDecorator
The TestDecorator class is part of the extensions to the junit framework. These classes provide
additional classes that are not considered essential to writing unit tests, but may provide useful
functionality in writing tests. The javadoc from JUnit describes this class ‘as the base class for defining
new test decorators. Test decorator subclasses can be introduced to add behaviour before or after a test is
run.’
3.4.4 TestSetup
The TestSetup class extends the TestDecorator class and its main purpose is to act as the base for
an anonymous test class that allows more control over the setUp and tearDown at levels higher than
each test method.
Its most useful application is trying to improve the time of execution for all unit tests by establishing the
testing environment once at the beginning of a test suite, instead of before and after each unit test.
Page 8 of 42
4 AN EXAMPLE
The basics steps involved with creating a unit test are fairly straightforward:
1. Define a test class that will hold unit tests
2. Initialise all objects needed for the unit test
3. Call operations on the objects involved in the unit test, stating what assertions or failures are
expected as result of the operations called.
4. Tear down the testing environment.
5. Execute the unit tests
import java.io.Serializable;
import java.util.Date;
/**
* This class is a simple test class that is intended to demonstrate how to
* construct a unit test to test the methods contained in this class.
*
* An instance of this class represents a count of the number of hits a web
* page may have. Each page may or may not have its own hit counter. A hit
* counter stores an internal number of hits that have been recorded, and
* the date at which the hit counter was first created.
*/
public class HitCounter implements Serializable
{
private int numberOfHits;
private Date creationDate;
/**
* Construct a new counter that does not have any hits as yet.
*/
public HitCounter()
{
creationDate = new Date();
numberOfHits = 0;
}
/**
* Returns the number of hits that this HitCounter has currently
* observed.
*
* @return The number of hits that this Hitcounter has observed.
*/
public int getNumberOfHits()
{
return numberOfHits;
}
/**
* Returns the date that this HitCounter was created.
*
* @return The date that this HitCounter was created.
*/
public Date getCreationDate()
{
return creationDate;
}
/**
* Triggers an increment in the number of hits held by this HitCounter.
*/
public void incrementNumberOfHits()
{
numberOfHits ++;
}
Page 9 of 42
/**
* Generate a unique hash integer for this HitCounter.
*
* @return a number that is a unique hash code for this HitCounter.
*/
public int hashCode()
{
int result;
result = numberOfHits;
result = 29 * result + creationDate.hashCode();
return result;
}
/**
* Implementation of the equals method for a HitCounter. A
* hit counter can be considered the same if and only if the
* number of hits and the creation date of the hit counters
* are the same.
*
* @return <code>true</code> if the object is considered the same as this
* HitCounter, otherwise <code>false</code>
*/
public boolean equals(Object object)
{
if ( object == null ) { return false; }
if ( this == object ) { return true; }
if ( !(object instanceof HitCounter) ) { return false; }
final HitCounter hitCounterToCompare = (HitCounter) object;
if (numberOfHits != hitCounterToCompare.numberOfHits) { return false; }
if (!creationDate.equals(hitCounterToCompare.creationDate))
{
return false;
}
return true;
}
/**
* Implementation of the standard toString() method, resulting
* in a string that describes the current values of this instance
* of the HitCounter.
*
* @return A string that represents a description of the current
* values of this HitCounter.
*/
public String toString()
{
return "HitCounter{" +
"numberOfHits=" + numberOfHits +
", creationDate=" + creationDate +
"}";
}
}
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.textui.TestRunner;
/**
*
*/
public class HitCounterTest extends TestCase (A)
{
public HitCounterTest(String testCaseName) (B)
{
super(testCaseName);
}
Page 10 of 42
public static void main(String[] args) (D)
{
TestRunner.run(suite());
}
}
(A) Any class that is expected to run as a test must have the TestCase class as its
superclass.
(B) All subclasses must override this constructor method that is defined by the TestCase
class. The testCaseName represents the name of a method defined in this class that
represents a unit test.
(C) The suite method provides a convenient method of automatically keeping the suite of
tests for this class up to date. JUnit will automatically treat any methods in this class
that start with test as a test method and add them to the test suite.
(D) Adding the main allows for any developer to automatically run all tests defined in the
suite method for this class. There are a number of different runners, for this example,
we are using a runner that produces results in a textual format.
/**
* Override the setUp method here to initialise objects that are tests
* need.
*/
protected void setUp()
{
hitCounter = new HitCounter();
}
...
}
Page 11 of 42
...
/**
* Tests that the increment number of hits method on the HitCounter
* class is operating as expected.
*
* When we effectively ‘hit’ the HitCounter, we should expect to
* increment exactly the number of times we hit it.
*/
public void testIncrementMethod()
{
final int CURRENT_NUM_OF_HITS = hitCounter.getNumberOfHits();
final int NUMBER_OF_HITS = 10;
for ( int i = 0; i < NUMBER_OF_HITS; i++ )
{
hitCounter.incrementNumberOfHits();
}
assertEquals("Number of expected hits was different",
CURRENT_NUM_OF_HITS + NUMBER_OF_HITS,
hitCounter.getNumberOfHits() );
}
...
/**
* This validates that the toString() method is operating correctly.
* toString() should produce a useful debugging string that details
* the values of its fields.
*/
public void testToString()
{
final int CURRENT_NUM_OF_HITS = hitCounter.getNumberOfHits();
final Date CREATION_DATE = hitCounter.getCreationDate();
final String EXPECTED_STRING = "HitCounter{numberOfHits=" +
CURRENT_NUM_OF_HITS + ", creationDate=" +
CREATION_DATE + "}";
assertEquals("toString() method does not produce the expected output",
EXPECTED_STRING, hitCounter.toString() );
}
...
Page 12 of 42
...
/**
* Clones an object via serialization. The object will be written to an
* in memory output stream, and read back in from that stream and
* reconstructed into the object that it should have been.
*
* @param serializableObject An object that is serializable
* @return A copy of the object where all serializable fields have the
* same value as that of the original.
* @throws IOException If there is a problem writing the object
* @throws ClassNotFoundException If the object passed in does not have
* its class definition loaded into memory.
*/
protected Object cloneViaSerialization( Object serializableObject )
throws IOException, ClassNotFoundException
{
// Serialize the object to memory
ByteArrayOutputStream inMemoryOutputStream =
new ByteArrayOutputStream();
ObjectOutputStream serializer =
new ObjectOutputStream(inMemoryOutputStream);
serializer.writeObject(hitCounter);
serializer.flush();
...
The actual test methods for testing the equals() method is shown below:
...
/**
* Test that a null passed into the equals() method returns false.
*/
public void testEqualsWithNullValue()
{
assertTrue("equals(null) does not result in a false boolean value",
!hitCounter.equals(null) ); // note the ! symbol
}
/**
* Test that validates that a call to the equals(Object) method with
* the same reference results in true.
*/
public void testEqualsWithSameReference()
{
assertTrue("equals(this) does not result in a true boolean value",
hitCounter.equals(hitCounter) );
}
/**
* Create an object that captures exactly the same data as the
* original hit counter, but are different references. Make sure
* that the equals() method returns true for the comparison
*/
public void testEqualsWithPerfectClone() throws Exception
{
HitCounter copyOfHitCounter =
(HitCounter)cloneViaSerialization(hitCounter);
assertTrue("equals() method of hitcounter should return true with a " +
“copy of a hitcounter",
hitCounter.equals(copyOfHitCounter));
assertTrue("equals() method should work in reverse",
copyOfHitCounter.equals(hitCounter));
}
...
Page 13 of 42
4.4.4 Testing the hashCode() method
Similar to the equals() method, the unit tests written for testing the hashCode() function leverage
the cloneViaSerialization(Object) method to create copies of a class.
...
/**
* Validate that the hash code for a hit counter and its perfect
* clone is the same.
*/
public void testHashCodeWithPerfectClone() throws Exception
{
HitCounter copyOfHitCounter =
(HitCounter)cloneViaSerialization(hitCounter);
assertEquals("Hashcode for the hit counter and its perfect clone ” +
“should be the same",
hitCounter.hashCode(), copyOfHitCounter.hashCode() );
}
/**
* Validate that the hash code is generated consistently regardless
* of the time that passes.
*/
public void testHashCodeIsConsistent() throws Exception
{
assertEquals("HashCode generated for the same object should be ” +
“consistent",
hitCounter.hashCode(), hitCounter.hashCode() );
}
...
Page 14 of 42
Figure 4: Running the GUI TestRunner
This results in the following interface being displayed on a windows machine.
Page 15 of 42
5 BEST PRACTICES
This section will discuss how to effectively create and manage your unit tests. It will recommend various
strategies for unit testing and how to get the most out of JUnit.
Test code is
parallel to
source code
Page 16 of 42
5.1.3 Define a standard naming convention for Test Classes
In many circumstances it is useful to be able to distinguish between classes that represent the code being
tested, the code that is testing it, and the code that organises the tests. A default naming convention that
may be adopted includes the following:
Leave source code class names with their original names.
The class that is testing the original class should be named of the same name, but appended with
Test.
The class that organises tests should have TestSuite appended to it.
The naming conventions will also help in executing the different suites from Ant as it can recursively call
the unit testing target against a number of classes that match a specified pattern. An example naming
pattern is depicted below:
Page 17 of 42
5.2 Writing Tests
5.2.1 Develop a unit test for every Java class (where possible)
A unit test should exist for every Java class that has been manually modified by the development group. It
is difficult to write unit tests for some classes due to the nature of that component (such as UI) so it may
not be feasible to write comprehensive unit tests for those aspects. But to quote Martin Fowler, “It is
better to write and run incomplete tests than not to run any complete tests.”
It is more important to write unit tests for code generators than it is for the code that was generated.
Maintaining unit tests for code that may change at any instant will prove unmanageable over time and
will not indicate where the problem originates.
...
...
It is also recommended that developers steer clear of ‘de-vowelling’ or writing non-descriptive test
methods names as these are useless to other developers in communicating the intention of the test method.
Example of test method names to avoid include: testScenario1, testSmMtdNull, testBlah, etc. It is much
better to provide a more-descriptive method name, even if it may be appear too long.
Page 18 of 42
5.2.3 Do not use the test-case constructor to set up a test case
Extracted from Javaworld’s “JUnit best practices” article8.
Setting up a test case in the constructor is not a good idea. Consider
public class SomeTest extends TestCase
{
public SomeTest (String testName)
{
super (testName);
// Perform test set-up
}
}
Imagine that while performing the setup, the setup code throws an IllegalStateException. In response,
JUnit would throw an AssertionFailedError, indicating that the test case could not be instantiated. The
stack trace that is generated proves rather uninformative; it only indicates that the test case could not be
instantiated. It doesn't detail the original error's location or place of origin. This lack of information makes
it hard to deduce the exception's underlying cause.
Instead of setting up the data in the constructor, perform test setup by overriding setUp(). Any exception
thrown within setUp() will thus be reported correctly.
5.2.4 Don’t assume the order in which testing within a test case run
Extracted from Javaworld’s “JUnit best practices” article9.
You should not assume that tests will be called in any particular order. Consider the following code
segment:
public class SomeTestCase extends TestCase
{
public SomeTestCase (String testName)
{
super (testName);
}
public void testDoThisFirst ()
{
...
}
public void testDoThisSecond ()
{
...
}
}
In this example, it is not certain that JUnit will run these tests in any specific order when using reflection.
Running the tests on different platforms and Java VMs may therefore yield different results, unless your
tests are designed to run in any order. Avoiding temporal coupling will make the test case more robust,
since changes in the order will not affect other tests. If the tests are coupled, the errors that result from a
minor update may prove difficult to find.
In situations where ordering tests makes sense -- when it is more efficient for tests to operate on some
shared data that establish a fresh state as each test runs -- use a static suite() method like this one to ensure
the ordering:
public static Test suite()
{
suite.addTest( new SomeTestCase("testDoThisFirst") );
suite.addTest( new SomeTestCase("testDoThisSecond") );
return suite;
}
8
JUnit best practices, JavaWorld
9
JUnit best practices, JavaWorld
Page 19 of 42
There is no guarantee in the JUnit API documentation as to the order your tests will be called in. JUnit’s
current implementation employs a Vector to store tests so you can expect the above tests to be executed in
the order they were added to the test suite. However this current implementation should not be relied
upon.
JUnit automatically catches exceptions. It considers uncaught exceptions to be errors, which means the
above example has redundant code in it.
10
JUnit best practices, JavaWorld
11
JUnit best practices, JavaWorld
12
JUnit best practices, JavaWorld
Page 20 of 42
Here is a far simpler way to achieve the same result:
public void exampleTest () throws SomeApplicationException
{
// do some test
}
In this example, the redundant code has been removed, making the test easier to read and maintain (since
there is less code). In addition to this, the stack trace produced from the error is available instead of just
the failure message that tells us that the test fails.
Use the wide variety of assert methods to express your intention in a simpler fashion. Instead of writing:
assert (creds == 3);
Write:
assertEquals ("The number of credentials should be 3", 3, creds);
The above example is much more useful to a code reader. And if the assertion fails, it provides the tester
with more information. JUnit also supports floating point comparisons:
assertEquals ("some message", result, expected, delta);
When you compare floating point numbers, this useful function saves you from repeatedly writing code to
compute the difference between the result and the expected value.
Use assertSame() to test for two references that point to the same object. Use assertEquals() to test for two
objects that are equal.
13
JUnit best practices, JavaWorld
14
JUnit best practices, JavaWorld
Page 21 of 42
public void testMethodManually()
{
final String ORIGINAL_STRING = “MyString”;
String convertedString = MyCode.formatStringUpper(ORIGINAL_STRING);
System.out.println(“Converted string is : “ + convertedString);
}
This not only removes the manual intervention required to execute the test completely, but it also further
demonstrates the intention of the test to other people who may read the code.
The following was extracted from Javaworld’s “JUnit best practices” article15.
Testing servlets, user interfaces, and other systems that produce complex output is often left to visual
inspection. Visual inspection -- a human inspecting output data for errors -- requires patience, the ability
to process large quantities of information, and great attention to detail: attributes not often found in the
average human being. Below are some basic techniques that will help reduce the visual inspection
component of your test cycle.
Swing - When testing a Swing-based UI, you can write tests to ensure that:
o All the components reside in the correct panels
o You've configured the layout managers correctly
o Text widgets have the correct fonts
o Basically all GUI widgets have the values of attributes set as expected. In other words,
you can test the model underlying the view of the components.
XML
o When testing classes that process XML, it pays to write a routine that compares two
XML DOMs for equality. You can then programmatically define the correct DOM in
advance and compare it with the actual output from your processing methods
Servlets
o With servlets, a couple of approaches can work. You can write a dummy servlet
framework and preconfigure it during a test. The framework must contain derivations of
classes found in the normal servlet environment. These derivations should allow you to
preconfigure their responses to method calls from the servlet.
You can avoid visual inspection in many ways. However, sometimes it is more cost-effective to use
visual inspection or a more specialised testing tool. For example, testing a UI's dynamic behaviour within
JUnit is complicated, but possible. It may be a better idea to purchase one of the many UI record/playback
testing tools available, or to perform some visual inspection as part of testing. However, that doesn't mean
the general rule -- don't visually inspect -- should be ignored
15
JUnit best practices, JavaWorld
Page 22 of 42
5.2.11 Catch the most specific exception
When writing tests for situations in which an exception is expected, it is extremely easy to write a test that
covers up other potential errors. Consider the following block of code:
public void testFileReaderWithNullInput()
{
try
{
mainClass.parseFile(null);
fail(“An exception should have been generated with a null input”);
}
catch ( Exception exception )
{
// exception expected
}
}
or worse yet:
public void testFileReaderWithNullInput()
{
try
{
mainClass.parseFile(null);
fail(“An exception should have been generated with a null input”);
}
catch ( Throwable throwable )
{
// exception expected
}
}
Although the parsing of a file with a null input is expected to throw an exception, the real problem is that
the two unit tests cover up any other exceptions or bugs that may be thrown by the code. The last catch
block in the above examples will catch all other types of exceptions that are thrown, so it is better to be as
specific as possible. The improved test case can be rewritten as below:
public void testFileReaderWithNullInput() throws IOException
{
try
{
mainClass.parseFile(null);
fail(“An exception should have been generated with a null input”);
}
catch ( NullPointerException nullPointerException )
{
// nullPointerException expected
}
}
Not only do we catch the NullPointerException that is expected, but throw the IOException
that the method declares, keeping the unit test clean, simple and readable.
5.2.12 Add at least one test case for every bug exposed
When a bug is logged against a system, it indicates that the behaviour of the system is not operating
correctly. A test case can be considered a way of defining the functionality of a system by capturing in a
number of assertions/failures the intended response. Thus, when a bug is defined, it means that a test case
should exist that should be failing.
If there was no unit test, then one should be written. Writing a unit test for the bug will not only clarify
what is causing the bug, and perhaps contribute to a better redesign of that sub-system, but once fixed,
will act as a monitor for that piece of code. If it fails to fulfil its contract with other parts of the system,
then developers will be notified that it has failed to do so.
Page 23 of 42
Due to the variety of different applications, it is difficult to design standards that guarantee performance,
so the performance tuning of unit tests should only be looked at when it is operating at unacceptable
levels. Apply different strategies for the different bottlenecks that may be causing unit tests to fail. The
most common is having setUp and tearDown methods that do too much. Some example strategies that
could be considered include:
Extracting common set up and tear down code to be executed once off before a series of tests
that all use the same things. An example of this code is given in Appendix C.
Loading pre-serialized objects into memory for instant comparable states instead of querying
them from a database.
Moving tests to be executed on a faster machine.
Pre-seeding data into a database.
Use of mock objects to replace external resources.
5.2.14 If it’s too hard to test, perhaps it’s hard to use – Refactor
Writing unit tests for some pieces of code is inherently difficult, but more often than not, unit tests should
be fairly simple to write. A unit test demonstrates how a method in a java class is supposed to be used, so
a good indication that a java class might need a redesign is that the unit test is difficult to write. The ‘code
smells’ that Martin Fowler refers to in his Refactoring book apply, but others indicators in unit tests that a
certain class may require refactoring include:
Unit tests must assert many conditions before calling the method they are testing.
Unit test setup and teardown is a long and tedious process.
A large number of assertions are required for each unit test and cannot be shared amongst other
unit tests that might be testing the same feature.
Each unit test method is extremely long and there is no common way of sharing a setup or
teardown for each method.
Developers spend excessive amounts of time in comparison to the time spent on the typical test-
code-test cycle.
Page 24 of 42
c. Java Class Data Factory - This is the programmatic way of specifying unit test data
and is one that java developers may feel more comfortable with. A data factory type of
class applies the extract class refactoring technique to loosen the coupling between the
unit tests and the creation of objects used by the unit tests. This technique is useful
when a number of non-related unit tests depend on the same objects (and their states)
for execution. Note that Java Class Data Factory can be used for managing either actual
or expected test data.
2. Managing Expected Data
a. Text File Data - An extension to JUnit, referred to as JUnit++, allows for configurable
unit tests. The text file externalises the test data into a number of properties, allowing
for highly configurable unit tests. This method is useful if you do not want to recompile
unit tests due to a change in the test (actual) data. This method leaves the test data
loosely coupled from the unit test, which may or may not be desirable depending on the
needs of the unit test. Healthcare implemented a variation of JUnit++ by creating a
ConfigurableTestCase class. It overcame some limitations of the JUnit++ extensions.
For example, JUnit++ requires a configuration file whereas it is optional with
ConfigurableTestCase. More information on the ConfigurableTestCase class (also
known as the JUnitBL extension) can be found in the Appendix.
16
Martin Fowler’s Article ‘Continuous Integration’ -
http://www.martinfowler.com/articles/continuousIntegration.html
Page 25 of 42
Ant Hill (http://www.urbancode.com/projects/anthill/) – A build management server that ensures
a controlled build process and promotes the sharing of knowledge within an organisation. It
allows for unit testing to be integrated with its cycle and has the ability to publish artefacts upon
completion.
Regardless of the tool used to automate the process, the continuous running and reporting of the entire
unit test suite for a project can be useful in monitoring the state of that piece of software.
Page 26 of 42
Selecting a class on the left hand pane results in the class being displayed on the right most panel like in
Figure 9. This detailed summary is a copy of the source file that the coverage report was run on. Each
significant line in the source file has two numbers. The first represents the line number in the file, the
second is the number of times that line was executed. Lines that were not executed are highlighted in a
pink colour, indicating the need for additional unit tests to be written for this block of code (assuming its
complexity makes it worthwhile testing).
Line not
executed
Number of times
executed
Executed line
Page 27 of 42
6 CONCLUSION
Testing remains an essential part of any software engineering process. Unit testing is one of the lowest
forms of testing, aimed at testing the individual components that an application may be built with. This
type of testing has been carried out historically ad hoc, with no formal way of way of writing and
managing unit tests. As a result, it has been typically neglected and the value it can add by improving
software quality and reducing future maintences costs are never realised.
With the acceptance of JUnit, the java-based unit testing framework as an industry de facto standard for
writing unit tests, any java-based software project can easily integrate unit testing into their development
environment. JUnit’s maturity as an open source project allow projects to leverage industry experience
and extensions to the framework all at a zero licensing cost.
JUnit is not the answer to all testing requirements, lacking the ability to easily test things such as GUIs
and EJB classes. It does not provide a complex reporting mechanism and sometimes it can time to
customise the framework for individual applications. There are a number of extensions or other testing
products that can be used to fulfill those requirements that JUnit cannot.
JUnit it centred around two main classes that both implement the Test interface. The TestCase class is
used as the container for defining unit tests, the other, the TestSuite class is used as a way of managing
those tests. There are also a number of classes that the framework provides for running unit tests that
report the results in a number of ways such as in textual or graphical format. This document has outlined
a number of best practices for setting up the test environment, writing the unit tests and running the tests.
It has also briefly discussed how BC4J components have been tested.
Unit testing is a great skill for any software engineer to develop as it can help in writing less bug-free
code. However it is important to keep in mind that having unit testing does not guarantee the quality of
the code. Thousands of poor quality unit tests will give a false impression of the quality of a piece of
software. Writing high quality unit tests will help in some way, but you must make sure that the code you
are testing is just as good.
Page 28 of 42
7 APPENDIX A: EXAMPLE CODE
7.1 HitCounter.java
package oracle.apps.example;
import java.io.Serializable;
import java.util.Date;
/**
* This class is a simple test class that is intended to demonstrate how to
* construct a unit test to test the methods contained in this class.
*
* An instance of this class represents a count of the number of hits a web
* page may have. Each page may or may not have its own hit counter. A hit
* counter stores an internal number of hits that have been recorded, and
* the date at which the hit counter was first created.
*/
public class HitCounter implements Serializable
{
private int numberOfHits;
private Date creationDate;
/**
* Construct a new counter that does not have any hits as yet.
*/
public HitCounter()
{
creationDate = new Date();
numberOfHits = 0;
}
/**
* Returns the number of hits that this HitCounter has currently
* observed.
*
* @return The number of hits that this Hitcounter has observed.
*/
public int getNumberOfHits()
{
return numberOfHits;
}
/**
* Returns the date that this HitCounter was created.
*
* @return The date that this HitCounter was created.
*/
public Date getCreationDate()
{
return creationDate;
}
/**
* Triggers an increment in the number of hits held by this HitCounter.
*/
public void incrementNumberOfHits()
{
numberOfHits ++;
}
/**
* Generate a unique hash integer for this HitCounter.
*
* @return a number that is a unique hash code for this HitCounter.
*/
public int hashCode()
{
int result;
result = numberOfHits;
result = 29 * result + creationDate.hashCode();
return result;
}
Page 29 of 42
/**
* Implementation of the equals method for a HitCounter. A
* hit counter can be considered the same if and only if the
* number of hits and the creation date of the hit counters
* are the same.
*
* @return <code>true</code> if the object is considered the same as this
* HitCounter, otherwise <code>false</code>
*/
public boolean equals(Object object)
{
if ( object == null ) { return false; }
if ( this == object ) { return true; }
if ( !(object instanceof HitCounter) ) { return false; }
/**
* Implementation of the standard toString() method, resulting
* in a string that describes the current values of this instance
* of the HitCounter.
*
* @return A string that represents a description of the current
* values of this HitCounter.
*/
public String toString()
{
return "HitCounter{" +
"numberOfHits=" + numberOfHits +
", creationDate=" + creationDate +
"}";
}
}
7.2 HitCounterTest.java
package oracle.apps.example;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.textui.TestRunner;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;
/**
* This class was designed to test all the methods that were added or modified
* in the oracle.apps.example.HitCounterTest class.
*
* Note that not this is not yet a comprehensive test suite for this methods
* contained in the HitCounter class.
*/
public class HitCounterTest extends TestCase
{
private HitCounter hitCounter;
Page 30 of 42
protected void setUp()
{
hitCounter = new HitCounter();
}
/**
* Tests that the increment number of hits method on the HitCounter
* class is operating as expected.
*
* When we effectively ‘hit’ the HitCounter, we should expect to
* increment exactly the number of times we hit it.
*/
public void testIncrementMethod()
{
final int CURRENT_NUM_OF_HITS = hitCounter.getNumberOfHits();
final int NUMBER_OF_HITS = 10;
for ( int i = 0; i < NUMBER_OF_HITS; i++ )
{
hitCounter.incrementNumberOfHits();
}
assertEquals("Number of expected hits was different",
CURRENT_NUM_OF_HITS + NUMBER_OF_HITS,
hitCounter.getNumberOfHits() );
}
/**
* This validates that the toString() method is operating correctly.
* toString() should produce a useful debugging string that details
* the values of its fields.
*/
public void testToString()
{
final int CURRENT_NUM_OF_HITS = hitCounter.getNumberOfHits();
final Date CREATION_DATE = hitCounter.getCreationDate();
final String EXPECTED_STRING = "HitCounter{numberOfHits=" +
CURRENT_NUM_OF_HITS + ", creationDate=" +
CREATION_DATE + "}";
assertEquals("toString() method does not produce the expected output",
EXPECTED_STRING, hitCounter.toString() );
}
/**
* Test that a null passed into the equals() method returns false.
*/
public void testEqualsWithNullValue()
{
assertTrue("equals(null) does not result in a false boolean value",
!hitCounter.equals(null) ); // note the ! symbol
}
/**
* Test that validates that a call to the equals(Object) method with
* the same reference results in true.
*/
public void testEqualsWithSameReference()
{
assertTrue("equals(this) does not result in a true boolean value",
hitCounter.equals(hitCounter) );
}
/**
* Create an object that captures exactly the same data as the
* original hit counter, but are different references. Make sure
* that the equals() method returns true for the comparison
*/
public void testEqualsWithPerfectClone() throws Exception
{
HitCounter copyOfHitCounter =
(HitCounter)cloneViaSerialization(hitCounter);
assertTrue("equals() method of hitcounter should return true with a " +
"copy of a hitcounter",
hitCounter.equals(copyOfHitCounter));
assertTrue("equals() method should work in reverse",
copyOfHitCounter.equals(hitCounter));
}
/**
* Validate that the hash code for a hit counter and its perfect
* clone is the same.
*/
public void testHashCodeWithPerfectClone() throws Exception
{
HitCounter copyOfHitCounter =
(HitCounter)cloneViaSerialization(hitCounter);
assertEquals("Hashcode for the hit counter and its perfect clone " +
"should be the same",
hitCounter.hashCode(), copyOfHitCounter.hashCode() );
}
Page 31 of 42
/**
* Validate that the hash code is generated consistently regardless
* of the time that passes.
*/
public void testHashCodeIsConsistent() throws Exception
{
assertEquals("HashCode generated for the same object should be " +
"consistent",
hitCounter.hashCode(), hitCounter.hashCode() );
}
/**
* Clones an object via serialization. The object will be written to an
* in memory output stream, and read back in from that stream and
* reconstructed into the object that it should have been.
*
* @param serializableObject An object that is serializable
* @return A copy of the object where all serializable fields have the
* same value as that of the original.
* @throws IOException If there is a problem writing the object
* @throws ClassNotFoundException If the object passed in does not have
* its class definition loaded into memory.
*/
protected Object cloneViaSerialization( Object serializableObject )
throws IOException, ClassNotFoundException
{
ByteArrayOutputStream inMemoryOutputStream =
new ByteArrayOutputStream();
ObjectOutputStream serializer =
new ObjectOutputStream(inMemoryOutputStream);
serializer.writeObject(hitCounter);
serializer.flush();
ByteArrayInputStream inMemoryInputStream =
new ByteArrayInputStream(inMemoryOutputStream.toByteArray() );
ObjectInputStream deserializer =
new ObjectInputStream(inMemoryInputStream);
return deserializer.readObject();
}
}
Page 32 of 42
8 APPENDIX B: ONE TIME SETUP
Sometimes it is necessary to improve the speed with which tests are run. When testing in an environment
where there is a large amount of set up and teardown, it might be easier to perform a one-time setup of
test data for an entire test suite instead of for each test.
Consider the following block of code. Although the test methods could be pushed into a single test
method, each has been separated to demonstrate the one-time setup principle.
package oracle.apps.example;
import junit.framework.TestCase;
import junit.framework.Test;
import junit.framework.TestSuite;
import java.util.Set;
/**
* This class validates that the titles (Mr, Mrs, Ms, and Miss) are always
* valid for the lookup scheme ‘TITLE_LOOKUP’.
*/
public class TitleLookupTest extends TestCase
{
/**
* The name of the lookup scheme for titles.
*/
public static final String TITLE_SCHEME_NAME = "TITLE_LOOKUP";
/**
* A class that lets us execute a set of defined SQL scripts. The script
* must exist relative to the base classpath.
*/
protected static SQLScriptRunner sqlScriptRunner = new SQLScriptRunner();
/**
* The resulting lookup set.
*/
protected Set titleLookupSet;
/**
* Set up the lookup values in the database
*/
protected void setUp() throws Exception
{
sqlScriptRunner.executeScript("/lookups/insert_title_lookups.sql");
// This finds all of the lookups for each test
LookupService lookupService = LookupService.getService();
titleLookupSet = lookupService.findLookupSet(TITLE_SCHEME_NAME);
}
/**
* Remove the lookup values in the database
*/
protected void tearDown() throws Exception
{
sqlScriptRunner.executeScript("/lookups/delete_title_lookups.sql");
}
/**
* Tests that the title ‘Mr’ is a valid title for the title lookup scheme.
*/
public void testValidTitleOfMr()
{
assertTrue("The title lookup scheme does not contain the valid " +
"title ‘Mr’", titleLookupSet.contains("Mr") );
}
/**
* Tests that the title ‘Ms’ is a valid title for the title lookup scheme.
*/
public void testValidTitleOfMs()
{
assertTrue("The title lookup scheme does not contain the valid " +
"title ‘Ms’", titleLookupSet.contains("Ms") );
}
Page 33 of 42
/**
* Tests that the title ‘Miss’ is a valid title for the title lookup
* scheme.
*/
public void testValidTitleOfMiss()
{
assertTrue("The title lookup scheme does not contain the valid " +
"title ‘Miss’", titleLookupSet.contains("Miss") );
}
/**
* Tests that the title ‘Mrs’ is a valid title for the title lookup
* scheme.
*/
public void testValidTitleOfMrs()
{
assertTrue("The title lookup scheme does not contain the valid " +
"title ‘Mrs’", titleLookupSet.contains("Mrs") );
}
/**
* Validate that the title of ‘Dr’ is not considered a valid title for
* the title lookup scheme.
*/
public void testInvalidTitleOfDr()
{
assertTrue("The title lookup scheme should not consider ‘Dr’ as a " +
"valid title", !titleLookupSet.contains("Dr") );
}
/**
* Validate that the number of titles in the title lookup scheme only
* contains 4 values.
*/
public void testNumberOfTitles()
{
assertEquals("Number of titles in the title scheme was incorrect",
4, titleLookupSet.size() );
}
/**
* Return a test suite containing all tests for this class.
* @return a test suite containing all tests for this class.
*/
public static Test suite()
{
return new TestSuite(TitleLookupTest.class);
}
}
The code listed above initialises the lookup seed data before every test and restores the database to its
original state after each unit test. This means that the SQL scripts are executed before and after every test.
This is only a simple example, but demonstrates that sometimes it would be more useful to perform a one
time set up for the entire test suite instead of once for every test to improve the performance.
To do this, we leverage the TestSetup class.
...
/**
* The SQL script has been pushed into the wrapper class for a one-time
* set up. We still set up variables that we need for unit tests here.
*/
protected void setUp()
{
Page 34 of 42
/**
* We wrap up the test suite in an anonymous extension to the TestSetup
* class. This lets us locally define a setUp() and tearDown() that is
* executed before the tests in the suite are executed.
*/
public static Test suite()
{
TestSuite suite = new TestSuite(TitleLookupTest.class);
TestSetup wrapperSuite = new TestSetup( suite )
{
protected void setUp()
{
sqlScriptRunner.executeScript(“/lookups/insert_title_lookups.sql”);
}
...
As shown in the code above, performing a one-time set up and teardown can help to drastically improve
the performance of some unit tests. This princple can be extended to apply to any point in a test hierarchy,
making it easy to help reduce the cost of common code being run for every single unit test.
Page 35 of 42
9 DOCUMENT REFERENCES
JUnit A Cook’s Tour
Webpage: http://junit.sourceforge.net/doc/cookstour/cookstour.htm
Written by: Erich Gamma and Kent Beck
Description: A description of the way in which JUnit is designed written by the original
authors Erich Gamma and Kent Beck.
JUnit Primer
Webpage: http://www.clarkware.com/articles/JUnitPrimer.html
Written by: Mike Clark
Description: A useful introduction to the JUnit framework. As mentioned on the webpage,
‘This article demonstrates how to write and run simple test cases and test suites
using the JUnit testing framework.’
JUnit
Webpage: http://www.junit.org
Description: The official home page for the website that hosts all the JUnit documentation.
Continuous Integration
Webpage: http://www.martinfowler.com/articles/continuousIntegration.html
Author: Martin Fowler
Description: An article that describes the benefits derived from continuous integration of
code changes and unit tests.
Page 36 of 42