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

Negative Tests on ProjectX

We set the mock MessagePublisher to throw an Exception after successfully publishing two Messages and then succeed from the fourth Message onwards. So the third row must be retained on the database if the PublishPurchaseOrder.publishNewPOs() is written to handle exceptions correctly.


public class PublishTestCase2 extends TestCase
{
      public void testPublishErrorWithoutPublisherWithMockTable()
                throws SQLException,
                InsufficientCredentialsException
    {
        final Registry registry =
                Registry.getInstance();

        final int publishErrorAt = 3;
        final int undoDeleteforRows = 1;

        //Here we use a DummyPOTable to simulate
        //a Database. But we would've used a mock
        //CachedRowSet even if there were a
        //Database since the idea here is to check
        //if the POMessage which could not be
        //published will still be retained in the
        //Database by invoking undoDelete() on
        //that row.

        final POTable poTable =
                getMockTableDemoForErrorTest(
                undoDeleteforRows);

        registry.setComponentImpl(POTable.class,
                poTable);

        /*
           Setup Mock MessagePublisher to throw
           Exception at publishErrorAt,
           then resume successfully.
        */

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

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

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

        //Expect to be called "publishErrorAt - 1"
        //number of times before Mock is made to
        //throw an exception.
        publisherControl.setVoidCallable(
                publishErrorAt - 1);

        try
        {
            publisher.publish(new POMessage());
        }
        catch (PublishException e)
        {
            //This can never happen because we are
            //just recording the calls.
        }
        publisherControl.setVoidCallable(1);
        publisherControl.setThrowable(
                new PublishException(
                "Message from Mock - " +
                "Publish error occurred!!"));

        try
        {
            publisher.publish(new POMessage());
        }
        catch (PublishException e)
        {
            //This can never happen because we are
            //just recording the calls.
        }
        //Publish will be called on the remaining
        //Messages.
        publisherControl.setVoidCallable(
        poTable.getNumOfNewPOs() - publishErrorAt);

        publisherControl.replay();

        registry.setComponentImpl(
               MessagePublisher.class, publisher);

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

        final int numOfPOsForDemo =
                poTable.getNumOfNewPOs();

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

        PublishPurchaseOrder publishPurchaseOrder =
                new PublishPurchaseOrder();

        final int published =
                publishPurchaseOrder.publishNewPOs();

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

        assertEquals(
              numOfPOsForDemo - undoDeleteforRows,
              published);
    }
        

The code below uses EasyMock to generate a "canned" CachedRowSet that is configured to have five new POs. It also sets it to expect an undo operation on undoDeleteforRows number of those POs. Then it sets the DummyPOTable to use this CachedRowSet for unit testing.


    private POTable getMockTableDemoForErrorTest(
                final int undoDeleteforRows)
                throws SQLException
    {
        final MockControl rowSetControl =
                MockControl.createNiceControl(
                CachedRowSet.class);

        final int newRowsForDemo = 5;

        final CachedRowSet rowSetForDemo =
                (CachedRowSet)
                rowSetControl.getMock();

        rowSetForDemo.next();
        rowSetControl.setReturnValue(true,
                newRowsForDemo);

        rowSetControl.setReturnValue(false,
                MockControl.ZERO_OR_MORE);

        rowSetForDemo.deleteRow();
        rowSetControl.setVoidCallable(
                newRowsForDemo);

        rowSetForDemo.undoDelete();
        rowSetControl.setVoidCallable(
                undoDeleteforRows);

        rowSetForDemo.acceptChanges();

        rowSetForDemo.close();

        rowSetControl.replay();

        //Since this is a demo, we'll simulate a
        //live Database by just returning these
        //data.

        return new DummyPOTable(newRowsForDemo,
                rowSetForDemo);
    }
}
        

The getMockTableDemoForErrorTest(...) method unveils the true power of EasyMock, by allowing us to create a mock/dummy CachedRowSet using just 11 lines of code. Otherwise, we would have been required to implement a total of around 248 methods in the CachedRowSet interface!

Now that we've designed our program carefully, you will also find its modular design invaluable when it comes to replicating or reproducing errors. Error conditions or production configurations can be easily setup to study and fix bugs quickly. Figures 1 and 2 show that the design is indeed modular, with implementations clearly separated from and dependent on abstract, stable classes in separate packages

Figure 1.
Figure 1. Dependencies inside of ProjectX are acyclic

Figure 2.
Figure 2. ProjectX's dependence on the common package is acyclic, and common is stable as it is made up mostly of abstract classes and interfaces

Since we are on the topic of writing bug-free code, there's another place where Java 1.5 features can prove to be very helpful. If you've worked on projects that required internationalization (I18N), then you must be familiar with ResourceBundles.

The most common way of creating locale-specific bundles is by using property files. But the problem with having message keys and values in the property files is that the code that refers to those messages uses plain strings. It is very possible to have spelling mistakes in either the code using the keys or in one of the several locale-specific bundles. Even if a key has been renamed in the bundle, the code can still continue to refer to the old key, since it is just a string and will compile successfully. There's no way of knowing if there's such a mistake until the whole project's code is executed and verified. That could be tiresome or just too late in the process.

Pages: 1, 2, 3, 4

Next Pagearrow