JUnit in the Computer Science Department
JUnit is a tool for writing tests, designed to fit in with the extreme programming philosopy of testing during development. You can download and install JUnit for yourself from:
the JUnit site
The site includes API documentation for the latest version (3.8.1 at the time of writing) and various tutorials:
API Documentation for version 3.8.1
Tutorial documentation
How JUnit is Installed in Computer Science
Like a lot of Java based developer's tools, the JUnit documentation tells you to change your classpath globally. Global environment variables are terrible (see footnote) so JUnit has been set up in Computer Science with a junit command which you can use in three ways:
The CS way The official way What it does
export CLASSPATH=.../junit.jar set up classpath
junit Test.java javac Test.java compile a test class
junit Test java junit.textui.TestRunner Test run a test class
junit java junit.swingui.TestRunner start up the JUnit GUI
You can still use the official route, if you can find out where the junit.jar file is installed (e.g. /usr/local/src/junit3.8.1/junit.jar on Linux, at the time of writing).
Installing JUnit Yourself
Perhaps the easiest way is to use the eclipse IDE which has JUnit built in. Eclipse is installed on the departmental machines, and some introductory documentation is available here. Otherwise, Download the JUnit zip file, unzip it, and put the junit.jar file somewhere sensible.
If you use Linux, you could copy and adapt the junit shell script from Computer Science. You can find out where it is using which junit.
If you use Windows, you could do the same with a batch script (which you would have to write), or you could set up your preferred Java tool (e.g. Textpad) to do the right thing. If all else fails, you can use the official route and change your CLASSPATH environment variable globally.
How you get at the environment variables is different on different versions of Windows, e.g. Control Panel -> System -> Environment Variables, or Control Panel -> System -> Advanced -> Environment Variables, or My Computer -> right click -> Properties -> Advanced -> Environment Variables, or Start -> Programs -> Accessories -> System Tools -> System Information -> Software Environment -> Environment Variables, or some other variation.
Then change the CLASSPATH variable. If there isn't one, create one. It's
value has to be typed by hand, and it must be character-perfect. The value
should be .;C:\junit\junit.jar which is a dot followed by a
semicolon followed by the full path of your junit.jar file, with
no spaces. (It is safer not to have any spaces in the path to your file, but
if you must, then the CLASSPATH value must be suitably quoted.) If you already
have a CLASSPATH, leave it intact and add a semicolon plus your path.
Using Junit
There are problems with the official documentation (at the time of writing). First, the tutorials don't start at the beginning by telling you how to write, compile and run tests. Second, they have lots of stuff about the background extreme programming philosophy and about how JUnit is implemented, mixed in with how to use it. Third, some of the material is out of date. So, here's yet another quick tutorial.
Getting Started
Getting started using v3.0+ of Eclipse is particularly easy (notes on how to use Eclipse)... you can create your own JUnit tests from within the IDE, by using the File -> New -> Other -> Java -> JUnit test case or JUnit test suite. A wizard will help you create your test case or suite, then you can create your Java test code using the editor. To run your tests, select the test case's or test suite's Java file in Eclipse's package explorer, right-click and select Run -> JUnit test.
Here is a minimal example to get started. First here's a class with one method that needs testing, and a program to test it:
Example.java
ExampleTest.java
Download these files and try them out with:
junit ExampleTest.java or javac ExampleTest.java junit ExampleTest or java junit.textui.TestRunner ExampleTest junit or java junit.swingui.TestRunner
The first command compiles the test program, and the second runs it. The third starts up the GUI, so you can run the tests repeatedly while developing the program.
The Example Explained
The program ExampleTest looks like this:
import junit.framework.*;
public class ExampleTest extends TestCase
{
public void testTail()
{
Example example = new Example();
String s = example.tail("steal");
assertEquals("teal", s);
}
public static Test suite()
{
return new TestSuite(ExampleTest.class);
}
public static void main(String args[])
{
junit.textui.TestRunner.run(suite());
}
}
The red parts are always the same. They link the program with the JUnit
library. Each test is a separate method with a name that starts with the four
characters test... The suite method tells JUnit to
look through the code for method names that start with test... and
run each one as an independent test. To add more tests, add more methods, each
with a different name, but all starting with test....
The main method isn't needed if you are using the lab
installation of JUnit. It allows you to run the tests by typing java
ExampleTest instead of java junit.textui.TestRunner
ExampleTest. It is polite to add this method even if you don't use it,
in case anyone else is going to use your testing file.
In some older documentation, the test methods are named individually in creating the test suite. You can still do that if you want, but the newer convention illustrated here seems easier.
Shared Code
To share example objects between tests, create global variables in
the ExampleTest class:
public class ExampleTest extends TestCase
{
Example example = new Example();
public void testTail()
{
String s = example.tail("steal");
assertEquals("teal", s);
}
public void testOneChar()
{
String s = example.tail("x");
assertEquals("", s);
}
...
}
The trouble is you can only do one-line initialising of global variables.
If you want more complex initialisation code than that, shared between tests,
you can define supporting methods (not starting with
test...):
Example example;
void init()
{
example = new Example();
}
public void testTail()
{
init();
...
}
public void testOneChar()
{
init();
...
}
The trouble with this is that every test method has to start with code to
call your init method. To avoid that, JUnit recognises a
particular method called setUp which it automatically
calls at the start of every test:
Example example;
public void setUp()
{
example = new Example();
}
public void testTail()
{
...
}
public void testOneChar()
{
...
}
Some sources show setUp declared as protected. That also
works, but public is more consistent with everything else about
using JUnit.
Assertions
In each test, you can make assertions about what the results ought to be.
These are what JUnit uses to decide if a test succeeds or not. To see the
range of assertion methods you can call, look at the API documentation for the
junit.framework.Assert class.
This is one place where some of the tutorial documentation is out of date.
In some places, they say you can use assert(), but the word
assert has become a Java keyword, so you now have to use
assertTrue() instead. Also, in some places it says you have to
write Assert.assertTrue() but you no longer have to do that
(because the TestCase class extends the Assert class). The main methods are:
assertTrue(b) b is any boolean expression assertEquals(expected, actual) compare two things of the same type assertEquals(e, a, margin) compare two doubles within a given margin fail() tell JUnit the test has failed
Most of these have versions which start with an extra string argument, to
customise the message that JUnit produces (which is especially useful with
fail), and versions with the test reversed. With
assertEquals, you must put the expected value first, as with:
assertEquals("teal", s);
Otherwise, JUnit's messages about failed tests don't make any sense.
Exceptions
There is one particularly tricky case. Suppose you want to assert that a particular call should cause an exception:
public void testEmpty()
{
String s = example.tail("");
}
You expect some kind of exception from this, because the
tail method is supposed to remove one character from the string,
and there aren't any. (In fact it if you try it, it causes a
StringIndexOutOfBoundsException exception.)
The solution is to put a fail call just afterwards, and then
put a try...catch block round the pair:
public void testEmpty()
{
try
{
String s = example.tail("");
fail("tail hasn't caused an exception when it should");
}
catch (Exception e)
{
}
}
If the call to tail correctly causes an exception, then control
jumps to the catch block, missing out the call to fail, and JUnit
treats the test as a success. If tail doesn't cause an exception,
then fail is called and JUnit reports a failed test. The catch
block is written here to catch all exceptions, on the basis that the exact form
of the exception might change as the code is developed, but that any exception
is acceptable in this case.
Footnote: Why Global Environment Variables are Terrible
- each user has to set them up separately before they can use the software that depends on them
- the values of the variables differ according to which directory the software happens to be installed in, as well as the platform
- the way in which the values are set up differ from one platform to another
- it is difficult for people to know whether environment variables are needed...
- or what they should be...
- or how to set them
- they have to be typed in by hand, so it is difficult to get them exactly right
- it is difficult to debug them when you get them wrong
- variables from one piece of software can clash with those from another piece of software
- more likely, variables from one version of a piece of software can clash with other versions
- if, for example, you change your global classpath variable to add a library for one program, it affects every program you compile or run
- maintenance of an installed piece of software on a shared server becomes almost impossible; if you want to move the software, you have to find all the people who have environment variables set, and get them to change the values
Ian Holyer

