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 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 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 executeQuery(String sql) {
try {
createStatement();
ResultSet results = statement.executeQuery(sql);
List 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.