Cover | Table of Contents | Colophon
UnitTest
, shown in Figure 2-1.
UnitTest is given in Example 2-1.UnitTest.java public abstract class UnitTest { protected static int num_test_success = 0; public abstract void runTest( ) throws Exception; protected void assertTrue( boolean condition, String msg ) throws Exception { if (!condition) throw new Exception(msg); num_test_success++; } }
UnitTest is abstract because its purpose
is to be the parent class for actual unit tests. It contains a static
integer member, num_test_success, which keeps
track of the number of successful tests. Descendant classes override
the method runTest( )
to run
actual tests. The method assertTrue()
tests a
condition. If the condition is TRUE, the
successful test counter is incremented. If it is
FALSE, an Exception is thrown
containing a message string associated with the condition.Library and be done. Adding a book to the library
is a feature with a little more to think through. We have a class
Book, and the fact that we can add a
Book to a Library suggests how
a Library should work. A
Library contains Books. The
ability to get a book from the library reinforces this idea.Book to a Library and then gets
the Book back out again, verifying that the
Library contains the Book.LibraryTest
is the initial unit test for the
Library
class. Its implementation is
shown in Example 2-6.LibraryTest.java public class LibraryTest extends UnitTest { public void runTest( ) throws Exception { Library library = new Library( ); Book expectedBook = new Book( "Dune" ); library.addBook( expectedBook ); Book actualBook = library.getBook( "Dune" ); assertTrue( actualBook.title.equals("Dune"), "got book" ); } }
Library and a
Book, adds the Book to the
Library, then gets the Book
from the Library and asserts that the expected
Book was found.TestRunner
to run LibraryTest, as
shown in Example 2-7.TestRunner.java
public class TestRunner {
public static void main(String[] args) {
TestRunner tester = new TestRunner( );
}
public TestRunner( ) {
try {
BookTest test = new BookTest( );
test.runTest( );
LibraryTest test2 = new LibraryTest( );
test2.runTest( );
System.out.println("SUCCESS!");
}
catch (Exception e) {
System.out.println("FAILURE!");
e.printStackTrace( );
}
System.out.println( UnitTest.getNumSuccess( )
+ " tests completed successfully" );
}
}TestCase,
TestRunner, TestFixture,
TestSuite, and TestResult.TestCase, the base class for a unit test. It is
shown in Figure 3-1.
TestCase. To
create a unit test, define a test class that is descended from
TestCase and add a test method to it. Example 3-1 shows the unit test
BookTest
.BookTest.java import junit.framework.*; public class BookTest extends TestCase { public void testConstructBook( ) { Book book = new Book("Dune"); assertTrue( book.getTitle( ).equals("Dune") ); } }
testConstructBook()
uses assertTrue()
to
check the value of the Book's
title. Test conditions always are evaluated by the
framework's assert methods. If a condition evaluates
to TRUE, the framework increments the successful
test counter. If it is FALSE, a test failure has
occurred and the framework records the details, including the
failure's location in the code. After a failure, the
framework skips the rest of the code in the test method, since the
test result is already known.TestCase
, TestRunner,
TestFixture, TestSuite, and
TestResult represent the core of the xUnit
architecture. To understand what they do is to understand how xUnit
works. Figure 3-6 shows how they all fit together.
LibraryTests is a TestSuite
containing BookTest and
LibraryTest. LibraryTest is a
TestFixture, and BookTest is a
TestCase. Conceptually,
TextTestRunner runs
LibraryTests, which runs
BookTest and LibraryTest, which
in turn run their test methods.Library and
Book code to implement the new features is given
at the end of the chapter.TRUE for the test to succeed. A plain assert, the
unit test for the
Library
method
removeBook( ), is shown in Example 4-1.LibraryTest.java
public void testRemoveBook( ) {
library.removeBook( "Dune" );
Book book = library.getBook( "Dune" );
assertTrue( book == null );
}
removeBook( ) is stubbed out, the
test fails. The following test results report the
failure:> java junit.textui.TestRunner LibraryTests
.....F.
Time: 0.06
There was 1 failure:
1) testRemoveBook(LibraryTest)junit.framework.AssertionFailedError
at LibraryTest.testRemoveBook(LibraryTest.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
FAILURES!!!
Tests run: 6, Failures: 1, Errors: 0
TRUE for the test to succeed. A plain assert, the
unit test for the
Library
method
removeBook( ), is shown in Example 4-1.LibraryTest.java
public void testRemoveBook( ) {
library.removeBook( "Dune" );
Book book = library.getBook( "Dune" );
assertTrue( book == null );
}
removeBook( ) is stubbed out, the
test fails. The following test results report the
failure:> java junit.textui.TestRunner LibraryTests
.....F.
Time: 0.06
There was 1 failure:
1) testRemoveBook(LibraryTest)junit.framework.AssertionFailedError
at LibraryTest.testRemoveBook(LibraryTest.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
FAILURES!!!
Tests run: 6, Failures: 1, Errors: 0
LibraryTest.java public void testRemoveBook( ) { library.removeBook( "Dune" ); Book book = library.getBook( "Dune" ); assertTrue( "book is not removed", book == null ); }
1) testRemoveBook(LibraryTest)junit.framework.AssertionFailedError: book is not removed
Library tests check a
Book's title attribute to verify
the expected Book object, as shown in Example 4-3 in the test method testGetBooks(
).LibraryTest.java
public void testGetBooks( ) {
Book book = library.getBook( "Dune" );
assertTrue( book.getTitle( ).equals( "Dune" ) );
book = library.getBook( "Solaris" );
assertTrue( book.getTitle( ).equals( "Solaris" ) );
}
Book is correct,
the tests should also check the
Book's author, but this means
adding extra asserts to each test. It's clearly
useful to have an assert method that compares an expected
Book to the actual Book,
checking all of the attributes. This new assert method is easy to
implement by building on the generic assertTrue()
method, as shown in Example 4-4.BookTest.java public class BookTest extends TestCase { public static void assertEquals( Book expected, Book actual ) { assertTrue(expected.getTitle( ).equals( actual.getTitle( ) ) && expected.getAuthor( ).equals( actual.getAuthor( ) )); } }
assertEquals()
takes expected and actual Book objects to compare.
It succeeds if the title and author attributes of the two
Books are equal. Example 4-5
shows how it is used.LibraryTest.java public class LibraryTest extends TestCase { private Library library; private Book book1, book2; public void setUp( ) { library = new Library( ); book1 = new Book("Dune", "Frank Herbert"); book2 = new Book("Solaris", "Stanislaw Lem"); library.addBook(
testGetBooks( ) method in the previous
section verifies that the Library contains two
Books, which is most clearly expressed as two
separate asserts, although they could be combined into one compound
condition. A single behavior can have several side effects that you
should check with separate assertions. So, it's not
a problem when a test method contains several
asserts, as long as the test method
is only testing a single behavior.LibraryTest.java
public void testLookupBooksByAuthor( ) {
// Add two books by same author
Book book3 = new Book( "Cosmos", "Carl Sagan" );
Book book4 = new Book( "Contact", "Carl Sagan" );
library.addBook( book3 );
library.addBook( book4 );
// Look up books by title and author
Book book = library.getBook( "Cosmos", "Carl Sagan" );
BookTest.assertEquals( book3, book );
book = library.getBook( "Contact", "Carl Sagan" );
BookTest.assertEquals( book4, book );
// Look up both books by author
Vector books = library.getBooks( "Carl Sagan" );
assertEquals( "two books not found", 2, books.size( ) );
book = (Book)books.elementAt(0);
BookTest.assertEquals( book3, book );
book = (Book)books.elementAt(1);
BookTest.assertEquals( book4, book );
}LibraryTest.java
public void testRemoveNonexistentBook( ) {
try {
library.removeBook( "Nonexistent" );
fail( "Expected exception not thrown" );
} catch (Exception e) {}
}
removeBook( ) method is called for a nonexistent
Book. If the exception is thrown, the unit test
succeeds. If it is not thrown, fail()
is
called. The fail( ) method is another useful
variation on the basic assert method. It is equivalent to
assertTrue(false), but it reads better.removeBook( ) method now throws an
exception, the
testRemoveBook( ) unit test should be updated, as
shown in Example 4-9.LibraryTest.java
public void testRemoveBook( ) {
try {
library.removeBook( "Dune" );
} catch (Exception e) {
fail( e.getMessage( ) );
}
Book book = library.getBook( "Dune" );
assertNull( "book is not removed", book );
}
fail( ) to cause the test to
fail when an unexpected exception is thrown. The
exception's message attribute is used as the assert
message.Book from the LibrarygetTitle( ) in Example 4-11.BookTest.java public void testGetTitle( ) { Book book = new Book( "Solaris", "Stanislaw Lem" ); assertEquals( "Solaris", book.getTitle( ) ); }
friend of the production class allows it to access
protected interfaces:class Library {
#ifdef TEST
friend class LibraryTest;
#endif
}
#ifdef TEST can omit such references when the
production code is built.PrivateAccessor that uses this approach to
access protected or private attributes and methods.com.utf.library,
com.utf.library.gui, and
com.utf.library.xml.
Library resides in the directory
src/com/utf/library, and the test class
LibraryTest is in
test/com/utf/library. The test
classes' package names parallel the production
classes' package names, so the test classes can
access and test protected behavior of the production code. Since the
code is in separate directory trees, it is simple to build and run
only the production or test code as desired.DBConnection
. We'll add the method
isOpen( )
to it, as shown in Example 4-18.DBConnection.java
public interface DBConnection {
void connect( );
void close( );
boolean isOpen( );
Book selectBook( String title, String author );
}
isOpen( ) method should verify that it returns
TRUE after connect(
)
is called, and
FALSE after close()
is called. The AbstractTest
class
AbstractDBConnectionTestCase
, shown in Example 4-19, provides these tests.AbstractDBConnectionTestCase.java
import junit.framework.*;
public abstract class AbstractDBConnectionTestCase extends TestCase {
public abstract DBConnection Book from a Library.Library class developed so far has a very
poorly performing algorithm to get a Book. It
serially reads through the collection of Books,
doing string comparisons on each one until the desired
Book is found. This awful lookup stratagem is
ideal for demonstrating a performance test that fails initially, but
succeeds after a little refactoring. Example 4-22
shows the unit test class
LibraryPerfTest
.