< Zurück | Inhalt | Weiter >

13.4.2 Using JUnit

To test out your installation, cd to the directory where you unpacked JUnit. If it isn’t already part of it, add the current directory (“.”) to your CLASSPATH:


$ export CLASSPATH="${CLASSPATH}:."


Then try:


$ java junit.swingui.TestRunner junit.samples.AllTests


You should see a Java Swing GUI appear, with a green bar showing the progress of the testing (Figure 13.3).



NOTE

You may see an error message like this in your terminal window:


(data/time) java.util.prefs.FileSystemPreferences checkLock... WARNING: Could not lock System prefs.Unix error code 136742412 (data/time) java.util.prefs.FileSystemPreferences syncWorld WARNING: Couldn't flush system prefs: java.util.prefs.Backi...


It will keep repeating as long as JUnit’s GUI is running. The easiest fix is to make the jre directory world-writable while you run the GUI the first time. It will create the files it needs (in a directory, .systemPrefs), and thereafter stop pestering you. Remember to change permissions on the directory back to their original value.

image



image


Figure 13.3 JUnit Swing GUI running tests


This is the GUI part of JUnit, part of what has made it so popular. By writing JUnit tests, you get to use their GUI. If you were to develop your own testing mechanism, you would also have to (re)invent a GUI.

There is an AWT GUI for the Swing-averse, but it is less featured. There is also a plain command-line test case runner:


$ java junit.textui.TestRunner junit.samples.AllTests

.........................................

.........................................

..................................... Time: 3.834


OK (119 tests)


$


It prints a period for each test that it runs. (Yes, there are 119 periods there. Go ahead; count them if you must.) The command-line version is useful for incorporating JUnit tests into shell scripts (e.g., for testing nightly builds, e-mailing the results) and is used by ant when it invokes JUnit.


13.5 WRITING TEST CASES


Writing a test case for your own Java code consists, at its simplest, of writing a new class for each class that you want to test. But this class that you create is built in a special way so that the test harness of JUnit can execute it. That is, the test case class that you create should meet certain naming conventions, so that the JUnit test runners can find what they need in order to run your tests. More specifically, your test cases will extend the JUnit class TestCase.

Now, TestCase is an abstract class, meaning there are parts that you have to fill in (i.e., methods that you must write) to make it a working class. Moreover, TestCase implements (in the Java sense of the word) the Test interface. Can you begin to see how the TestCase class is a framework? It defines the rough outline of how the test cases will look so that a common test runner can run any test case, no matter who wrote it.

Let’s look at a simple example, to see what such a test case looks like.

Example 13.2 shows one for testing our Account class.


image

Example 13.2 Simple test case

package net.multitool.core;


import java.util.*; // needed by our class import net.multitool.util.*; // needed by our class


import junit.framework.*; // needed by JUnit


/**

* for JUnit testing of Account.java

*/

public class AccountTest

extends TestCase

{

// our test instrumentation: Account base;


// run before each test case: protected void

setUp()

{

base = new Account("Base", new User("testuser"), "150");

}


// our one test case public void testCreateSub()

{

// Create a subaccount, assigning $50 of our pool of $150. Account sub1 = base.createSub("sub1", "50");

// Make sure that it created something. assertNotNull("Couldn't create sub1", sub1);


// Now a 2nd subaccount.

Account sub2 = base.createSub("sub2", "75"); assertNotNull("Couldn't create sub2", sub2);


// Now a 3rd subaccount, to use up all the $. Account sub3 = base.createSub("sub3", "25"); assertNotNull("Couldn't create sub3", sub3);


// We should have the same total that we started with. assertEquals(150, base.getTotal().getDollars());


// We should have used up all our $. assertEquals(0, base.getBalance().getDollars());


// Be sure the (sub)account lookup works: Account ex2 = base.getSub("sub2"); assertNotNull("Couldn't find sub2", ex2); assertSame(sub2, ex2);


} // testCreateSub


} // class AccountTest


image


Notice how we’ve named our test case class. We take the name of the class and append Test to the end. This is convenient for us—we can easily see which classes have test cases; but more importantly, JUnit can use this and other naming conventions to derive the test case names (more on that later). Notice also that the method in the Account class that we want to test, called createSub(), gets exercised by a method named testCreateSub()—we


Table 13.1 JUnit Naming


image


image

Class Method

In your original code


image

MyClass myMethod

In your test case MyClassTest testMyMethod


prepend the word “test” to the method name, and capitalize the now-no-longer- first letter. Again, JUnit will use this naming convention, along with introspection, to automatically derive the test names from the actual method names (more on that later, too). The naming conventions we’ve seen so far are summarized in Table 13.1.

Let’s take a quick look at the code. We import the framework for JUnit test cases, so that the compiler can resolve the names that deal with JUnit stuff. The TestCase class that we extend is part of that JUnit stuff. It’s an abstract class that defines much of what we use for testing. We just fill in what we need. The TestCase class defines a method called setUp(). The setUp() method is called not just once, but before every test method is called. That way you can initialize variables and get into a known state before each test. Since it’s already defined in the TestCase class, we can override it (as in our example) to do what we want, or we can not include it in our class and get the default

behavior from TestCase (which is to do nothing).

There is also a method named tearDown() which you can override if you need to close things up at the end of a test case (e.g., close a database connec- tion). As with setUp(), its default behavior, as defined in TestCase, is to do nothing.

The test case itself—the method where we will exercise our class—is called testCreateSub (since we want to test our createSub() method). Inside such a method (and we could have more than one) we write code which uses the objects in our application. Then at various junctures in the code we make asser- tions about the state of things—for example, this variable should be non-null, or this expression should have this particular value.

Those assertions are, to our way of thinking, the tests. We’re testing to see if the subaccount was created, or if the main account did, indeed, use up all of its dollars in allocation to the subaccounts. But they are not what is called tests by JUnit. Rather, each individual method in a test class is considered a single test. Such test methods are, typically, a collection of assertions surrounding the use of a single (application) method. So in our example, the method


testCreateSub() is a single JUnit test which asserts various conditions about various invocations of the createSub() method. Note that all of the assertions encountered in the execution of the test class must pass for the test to pass.

So what happens if an assertion fails? The assert method will throw an ex- ception, reporting the failure. In JUnit terminology, a failure is a test that didn’t pass, whereas an error is a problem with the running of the test. A missing class or a null pointer exception are errors, whereas an assertNotNull() call failing is considered a test failure.

The handy thing about the exceptions that the assert methods throw is that they are, technically speaking, not java.lang.Exception throwables but rather belong to the java.lang.Error type of throwable. (Don’t confuse this technical Java use of the word “error” with our more informal use in the previ- ous discussion of failure versus error.) To quote from the Javadoc page for java.lang.Error:


A method is not required to declare in its throws clause any subclasses of Error that might be thrown during the execution of the method but not caught, since these errors are abnormal conditions that should never occur.


So the use of Error by JUnit’s various assert methods is done simply as a convenience for us test developers, so that we don’t have to put throws ... clauses on all of our method declarations.