Jeff's Blog

Musings about software development, Java, OO, agile, life, whatever.


Thursday, November 03, 2005 
Database TDD Part 16: Driving Tests From the Application

Someone asked about the package organization of the project. In response, I moved the persistence-related classes into the persistence package. This required I believe one change to access level, and a number of import changes (which of course Eclipse will fix automatically).

I chose not to move the data access classes themselves--CustomerAccess, UserAccess, and associated test classes--to a different package yet. Keeping them with domain classes allows for some things to be stated at package level instead of public. Less work for now, in any case. I'm not worried about any level of effort in the future, since changes like these are easy and safe using Eclipse.

The new package organization:

  • domain.UserTest
  • domain.User
  • domain.UserAccessTest
  • domain.UserAccess
  • domain.CustomerTest
  • domain.Customer
  • domain.CustomerAccessTest
  • domain.CustomerAccess
  • persistence.DataAccess<T>
  • persistence.JdbcAccessTest
  • persistence.JdbcAccess
  • persistence.JdbcAccessExceptionsTest
  • persistence.JdbcException
  • persistence.Persistable
  • persistence.PersistableMetadata<T>
  • persistence.PersisterTest
  • persistence.Persister
  • persistence.SqlGeneratorTest
  • persistence.SqlGenerator
It'll work for now.

The same someone was also concerned about the tests having to access the database so much. To start figuring out what's going to work best, I want to write some application-level code.

package application;

import junit.framework.*;

public class ApplicationTest extends TestCase {
   public void testVerifyUser() {
      final String name = "name";
      final String password = "password";
      Application application = new Application();
      assertFalse(application.isRegistered(name, password));
      application.registerUser(name, password);
      assertTrue(application.isRegistered(name, password));
   }
}
package application;

import domain.*;

public class Application {
   public void registerUser(String name, String password) {
      User user = new User(name, password);
      new UserAccess().save(user);
   }

   public boolean isRegistered(String name, String password) {
      User user = new UserAccess().find(name);
      return user != null;
   }
}
OK, there's a simple test, a failing one as expected. It'll be my beacon for guidance over the next several minutes.

The test immediately introduces a problem: I don't properly handle an empty result set in the JdbcAccess class. Driving a solution to this through tests, I first add a test for the Persister class that indicates what I want to happen when no rows are returned:

   public void testFindNotFound() {
      assertNull(persister.find(BAD_KEY));
   }
Other interesting elements in PersisterTest:
   private static final String BAD_KEY = "not found";

   protected void setUp() {
      access = new JdbcAccess() {
         ...
         public List<String> executeQuery(String sql) {
            lastSql = sql;
            if (sql.indexOf(BAD_KEY) > -1)
               return null;
            return EXPECTED_ROW;
         }
      };
The change to Persister is minor:
   public T find(String key) {
      String sql = new SqlGenerator().createFindByKey(metadata.getTable(), metadata.getColumns(),
            metadata.getKeyColumn(), key);
      List<String> row = access.executeQuery(sql);
      if (row == null)
         return null;
      return metadata.create(row);
   }
The Persister change in turn triggers the need for changes in JdbcAccess via JdbcAccessTest:
   public void testExecuteQueryNoResults() {
      access.execute(createTableSQL());
      assertNull(access.executeQuery("select x from " + TABLE + " where 1 = 0"));
   }
JdbcAccess:
   public List<String> executeQuery(String sql) {
      try {
         createStatement();

         ResultSet results = statement.executeQuery(sql);
         List<String> row = null;
         if (results.next())
            row = getRow(results);
         results.close();

         connection.close();
         return row;
      }
      catch (SQLException e) {
         throw new JdbcException(sql, e);
      }
   }

Now I'm back to the original Application test, which is now failing. Why? Well, because it's dealing with the database, and a previous run of the test added the user in question to the database tables.

There are a few options for a solution. The most obvious is to make sure that the database is cleaned out with each new unit test run. The better tactic is to start looking at inserting mocks. The dependence on the state of the database is one of many reasons we'll want mocks for our unit tests. Other reasons are availability of the database, contention during concurrent test runs, and speed of the entire test run.

I'll start dealing with these issues tomorrow.
Today's code


Comments: Post a Comment

Links to this post:

Create a Link



<< Home

RSS Feed (XML)

Archives

February 2004   March 2004   May 2004   September 2004   October 2004   January 2005   February 2005   September 2005   October 2005   November 2005   December 2005   January 2006   February 2006   March 2006   June 2006   August 2006   January 2007   February 2007   March 2007   April 2007   September 2007   October 2007   November 2007   December 2007   January 2008  

This page is powered by Blogger. Isn't yours?