Just like regular classes, abstract classes and interfaces should have their own unit tests. Designing such tests is not straightforward, because these object types cannot be directly instantiated . We'd also like to ensure that every descendant of an abstract class passes the parent object's tests. The AbstractTest pattern is the answer. An AbstractTest is itself abstract, like the tested object. It contains an abstract factory method, which produces an instance of the object to test. It also contains the test methods for the abstract class. They resemble ordinary unit test methods , but test instances of the abstract class created by the factory method. To test a concrete class that is descended from the abstract class, the unit test is subclassed from the AbstractTest. Its factory method returns an instance of the concrete class. When the concrete unit test is run, the AbstractTest is run as well. So, the AbstractTest tests every concrete implementation of the abstract class. Let's create an AbstractTest for the interface DBConnection . We'll add the method isOpen( ) to it, as shown in Example 4-18. Example 4-18. The interface DBConnection DBConnection.java public interface DBConnection { void connect( ); void close( ); boolean isOpen( ); Book selectBook( String title, String author ); } The AbstractTest should test the behavior of the interface to make sure that any concrete implementation of it is correct. Tests of the 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. Example 4-19. The AbstractTest class AbstractDBConnectionTestCase AbstractDBConnectionTestCase.java import junit.framework.*; public abstract class AbstractDBConnectionTestCase extends TestCase { public abstract DBConnection getConnection( ) ; public void testIsOpen( ) { DBConnection connection = getConnection( ) ; connection.connect( ); assertTrue( connection.isOpen( ) ); } public void testClose( ) { DBConnection connection = getConnection( ) ; connection.connect( ); connection.close( ); assertTrue( !connection.isOpen( ) ); } } The AbstractTest specifies a factory method, getConnection() . Concrete tests that descend from it will implement the factory method, allowing the test methods testIsOpen( ) and testClose( ) to test an instance of the concrete class. Notice how these methods use getConnection() to get the DBConnection to test. AbstractTests have names ending in "TestCase," which is different from other test classes. A separate naming convention for AbstractTest classes makes them easily recognizable. Some unit test tools assume that any class named ending with "Test" are test classes that should be instantiated and run, and the different naming convention avoids confusion. To see the AbstractTest run, we need to define a concrete class descended from DBConnection and a corresponding concrete unit test descended from AbstractDBConnectionTestCase . The concrete class JDBCConnection is shown in Example 4-20. Example 4-20. The concrete class JDBCConnection JDBCConnection.java public class JDBCConnection implements DBConnection { private String connectString ; private boolean open ; public JDBCConnection ( String connect ) { connectString = connect; open = false; } public void connect( ) { open = true; } public void close( ) { open = false; } public boolean isOpen( ) { return open; } public String getConnectString( ) { return connectString; } public Book selectBook ( String title, String author ) { return null; } } JDBCConnection is an initial version of an interface to a JDBC database engine. It differs from the base DBConnection by its member connectString , which contains the URL of a JDBC database connection. The unit test JDBCConnectionTest tests JDBCConnection . It is derived from the AbstractTest. It is shown in Example 4-21. Example 4-21. The concrete test JDBCConnectionTest JDBCConnectionTest.java public class JDBCConnectionTest extends AbstractDBConnectionTestCase { public DBConnection getConnection( ) { return new JDBCConnection( "jdbc:odbc:testdb" ); } public void testConnectString( ) { JDBCConnection connection = (JDBCConnection) getConnection( ) ; String connStr = connection.getConnectString( ); assertTrue( connStr.equals("jdbc:odbc:testdb") ); } } JDBCConnectionTest implements the factory method getConnection( ) and one test method, testConnectString( ) . When the test is instantiated and run, the two test methods in the parent AbstractTest also will be run to test instances of JDBCConnection . This way, the AbstractTest verifies that the concrete subclass passes the tests of the parent interface. |