ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Towards Bug-Free Code
Pages: 1, 2, 3, 4

ProjectX: An Example

We will consider a fictitious project called "ProjectX." Let's assume that there are just two very simple use cases:

  1. Add a purchase order to a database.
  2. Retrieve new purchase orders from the database and publish them to a messaging system for processing.

Obviously, there are two main modules here: the messaging subsystem and the database access subsystem.

Let's say that the developer who is supposed to develop the messaging module falls sick and goes on leave for two weeks, leaving the module half-done. The only things that the developer implementing the second use case has are the database access module and the interfaces to both subsystems that were designed when the team worked together in the first few weeks of the project.

The crucial part of this implementation cycle is to continue with development and testing without the messaging module. This can be accomplished by providing a dummy implementation of the messaging interfaces. And the easiest way is to replace the proposed implementation with the dummy.


public class UseCase2
{
    public void retrieveAndPublishPOs()
    {
        MessagePublisher messagePublisher =
                new DummyMessagePublisher();

        ....

        /*
         Retrieve POs from Database
         and publish each one.
        */

        messagePublisher.publish(po);

        ....
    }
}
        

When the messaging module is ready, DummyMessagePublisher can be replaced by the actual implementation. However, you'll notice that even though we have taken the trouble of extracting an interface for the MessagePublisher, we've still not been able to break free from its implementation because the new DummyMessagePublisher() will have to be replaced each time by whatever implementation we come up with. Which brings us back to square one. This is where providing "hooks" into the system allows for a lot of flexibility. Consider how easy the whole thing would have been, had we used:


MessagePublisher messagePublisher =
                Registry.getImplFor(
                MessagePublisher.class)

//And set the "impl" using this
registry.setComponentImpl(MessagePublisher.class,
                new DummyPublisher())

//or this in the setUp() method.
registry.setComponentImpl(MessagePublisher.class,
                new MyJMSPublisher())
        

Unit-testing ProjectX

Now that we have made accommodations for introducing any implementation for the interfaces, we'll see how easy it is to unit test this use case even without the messaging subsystem.


import common.registry.Registry;
import junit.framework.TestCase;
import org.easymock.MockControl;
import projectx.data.POMessage;
import projectx.database.POTable;
...
import projectx.messaging.MessagePublisher;
...
import projectx.usecases.usecase2.
                PublishPurchaseOrder;

public class PublishTestCase extends TestCase
{
    @Override protected void setUp()
                throws Exception
    {
        final Registry registry =
                Registry.getInstance();

        //--- setup OraclePOTable ---

        final OraclePOTable oraclePOTable =
                new OraclePOTable();

        registry.setComponentImpl(POTable.class,
                oraclePOTable);

        //--- setup Mock MessagePublisher ---

        final MockControl publisherControl =
                MockControl.createControl(
                MessagePublisher.class);

        final MessagePublisher publisher =
                (MessagePublisher)
                publisherControl.getMock();

        //Record the methods that will be invoked.
        try
        {
            publisher.publish(new POMessage());
        }
        catch (PublishException e)
        {
            //This can never happen because we are
            //just recording the calls.
        }

        //Expect to be called getNumOfNewPOs()
        //number of times.
        publisherControl.setVoidCallable(
                oraclePOTable.getNumOfNewPOs());

        //Set the MockControl to replay the
        //recorded methods.
        publisherControl.replay();

        registry.setComponentImpl(
                MessagePublisher.class,
                publisher);
    }

 public void testSuccesslPublishWithoutPublisher()
                throws SQLException,
                InsufficientCredentialsException
    {
        final POTable poTable =
                Registry.getImplFor(POTable.class);

        final int numOfPOsForDemo =
                poTable.getNumOfNewPOs();

        //---------------------------------------

        PublishPurchaseOrder publishPurchaseOrder
                = new PublishPurchaseOrder();

        final int published =
                publishPurchaseOrder.publishNewPOs();

        //---------------------------------------

        assertEquals(numOfPOsForDemo, published);
    }
}
        

If you read the setUp() method of this JUnit test case, you will see that it requires just four lines of code using the EasyMock library to place a mock implementation of MessagePublisher and set it up to expect the publish() invocation a certain number of times. The testSuccessfulPublishWithoutPublisher() test makes note of the number of new POs that have to be published using poTable.getNumOfNewPOs() before running the test. Then, it runs the actual test of invoking publishPurchaseOrder.publishNewPOs(), which returns the number of POs that were retrieved from the database and successfully published to the MessagePublisher.

The PublishPurchaseOrder's publishNewPOs() method looks like this:


public int publishNewPOs() throws SQLException,
                InsufficientCredentialsException
{
    final POTable poTable =
                Registry.getImplFor(POTable.class);

    final MessagePublisher publisher =
                Registry.getImplFor(
                MessagePublisher.class);

    int success = 0;
    final CachedRowSet rowSet =
                poTable.getNewPOs();

    while (rowSet.next())
    {
        POMessage tempMessage = new POMessage();
        /*
         Populate the PO object with the data in
         the current row.
        */
        try
        {
            publisher.publish(tempMessage);
            rowSet.deleteRow();

            success++;
        }
        catch (PublishException e)
        {
            logger.error("Error occurred while " +
                "publishing PO: " +
                tempMessage.toString(), e);

            try
            {
                rowSet.undoDelete();
            }
            catch (SQLException e1)
            {
                logger.error("Undo operation on" +
                deleted PO failed: " +
                tempMessage.toString(),
                e1);
            }
        }
    }

    rowSet.acceptChanges();
    rowSet.close();

    return success;
}
        

Again, publishNewPOs() must rely only on interfaces (MessagePublisher and POTable) to remain useful.


public interface POTable extends Component
{
    /**
     * @param message
     * @return The Order number of this newly
     * created PO.
     * @throws SQLException
     */
    public long addPO(POMessage message)
                throws SQLException,
                InsufficientCredentialsException;

    public int getNumOfNewPOs()
                throws SQLException;

    public CachedRowSet getNewPOs()
                throws SQLException,
                InsufficientCredentialsException;

    public void deletePO(POMessage message)
                throws SQLException,
                InsufficientCredentialsException;
}

public interface MessagePublisher extends Component
{
    public void publish(Message msg)
                throws PublishException;
}
        

After having written this test case, which covers only the "success path," we soon run out of excuses for not writing tests. So the next step would be to write a test case and see how the PublishPurchaseOrder.publishNewPOs() fares when the MessagePublisher throws an Exception in the publish(...) method.

This time, in addition to using a mock MessagePublisher, we will also use a DummyPOTable that uses a mock CachedRowSet to record and finally, verify that error-handling/compensating methods get invoked when a PO does not get published successfully.

Pages: 1, 2, 3, 4

Next Pagearrow