- 对数据库访问的单元测试--------数据库访问代码用一套持久化API访问你的数据库.这类测试的目标是验证你是否正确使用了持久化API.测试策略主要是采用mock objects来模拟持久化API在不与数据库连接并且不在容器内的情况下运行测试. 现在我们来测试数据库访问代码.JDBC,JDO等等本身设计得很好,并且使用java接口,这样一来就非常适宜于mock objects策略.对于 JDBC API 来说,有现成的mock objects包(MockObjects.com JDBC API),它比较成熟,已经为JDBC代码单 元测试作好了准备.下面是我们要测的数据库访问代码:
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.commons.beanutils.RowSetDynaClass;
public class JdbcDataAccessManager implements DataAccessManager
{
private DataSource dataSource;
public JdbcDataAccessManager() throws NamingException
{
this.dataSource = getDataSource();
}
// depending on application server
protected DataSource getDataSource() throws NamingException
{
InitialContext context = new InitialContext();
DataSource dataSource =
(DataSource) context.lookup("java:/DefaultDS");
return dataSource;
}
/* create a javax.sql.DataSource on BEA Weblogic
- at first you need to use the Weblogic console http://localhost:7001/console to configure a new connection pool, and a new DataSource associated with this pool
http://edocs.bea.com/wls/docs81/jndi/jndi.html
http://dev2dev.bea.com/pub/a/2004/01/142.html
protected DataSource getDataSource() throws NamingException
{
}Hashtable ht = new Hashtable();
ht.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
ht.put(Context.PROVIDER_URL,
"t3://localhost:7001");
Context context = new InitialContext(ht
);
return getConnection(context, "definedDataSourceName");
protected Connection getConnection(Context context, String datasource) throws SQLException
{
return (javax.sql.DataSource) context.lookup(datasource);
}
*/
protected Connection getConnection() throws SQLException
{
return this.dataSource.getConnection();
}
public Collection execute(String sql) throws Exception
{
Connection connection = getConnection();
// For simplicity, we'll assume the SQL is a SELECT query
ResultSet resultSet =
connection.createStatement().executeQuery(sql);
RowSetDynaClass rsdc = new RowSetDynaClass(resultSet);
resultSet.close();
connection.close();
return rsdc.getRows();
}
}
数据库访问代码主要在execute方法内,我们要给里面的使用到的JDBC API提供mock objects,首先我们需要模拟 Connection对象,接着模拟的Connection对象就可以返回mock ResultSet,mock Statement等等.现在我们关 心的问题是如何将一个mock Connection对象传递给JdbcDataAccessManager类.很简单,再写一个 JdbcDataAccessManager的子类,添加setter方法,我们测了这个子类也就相当于测了DataAccessManager类.
import java.sql.Connection;
import java.sql.SQLException;
import javax.naming.NamingException;
import javax.sql.DataSource;
public class TestableJdbcDataAccessManager
extends JdbcDataAccessManager
{
private Connection connection;
public TestableJdbcDataAccessManager() throws NamingException
{
super();
}
public void setConnection(Connection connection)
{
this.connection = connection;
}
protected Connection getConnection() throws SQLException
{
return this.connection;
}
protected DataSource getDataSource() throws NamingException
{
return null;
}
}
下面我们来编写测试代码:
import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import org.apache.commons.beanutils.DynaBean;
import com.mockobjects.sql.MockConnection2;
import com.mockobjects.sql.MockResultSetMetaData;
import com.mockobjects.sql.MockSingleRowResultSet;
import com.mockobjects.sql.MockStatement;
import junit.framework.TestCase;
public class TestJdbcDataAccessManagerMO4 extends TestCase
{
private MockSingleRowResultSet resultSet;
private MockResultSetMetaData resultSetMetaData;
private MockStatement statement;
private MockConnection2 connection;
private TestableJdbcDataAccessManager manager;
protected void setUp() throws Exception
{
resultSetMetaData = new MockResultSetMetaData();
resultSet = new MockSingleRowResultSet();
resultSet.setupMetaData(resultSetMetaData);
statement = new MockStatement();
connection = new MockConnection2();
connection.setupStatement(statement);
manager = new TestableJdbcDataAccessManager();
manager.setConnection(connection);
}
protected void tearDown()
{
connection.verify();
statement.verify();
resultSet.verify();
}
public void testExecuteOk() throws Exception
{
String sql = "SELECT * FROM CUSTOMER";
statement.addExpectedExecuteQuery(sql, resultSet);
String[] columnsUppercase = new String[] {"FIRSTNAME",
"LASTNAME"};
String[] columnsLowercase = new String[] {"firstname",
"lastname"};
String[] columnClasseNames = new String[] {
String.class.getName(), String.class.getName()};
resultSetMetaData.setupAddColumnNames(columnsUppercase);
resultSetMetaData.setupAddColumnClassNames(
columnClasseNames);
resultSetMetaData.setupGetColumnCount(2);
resultSet.addExpectedNamedValues(columnsLowercase,
new Object[] {"John", "Doe"});
connection.setExpectedCreateStatementCalls(1);
connection.setExpectedCloseCalls(1);
Collection result = manager.execute(sql);
Iterator beans = result.iterator();
assertTrue(beans.hasNext());
DynaBean bean1 = (DynaBean) beans.next();
assertEquals("John", bean1.get("firstname"));
assertEquals("Doe", bean1.get("lastname"));
assertTrue(!beans.hasNext());
}
public void testExecuteCloseConnectionOnException()
throws Exception
{
String sql = "SELECT * FROM CUSTOMER";
statement.setupThrowExceptionOnExecute(
new SQLException("sql error"));
connection.setExpectedCloseCalls(1);
try
{
manager.execute(sql);
fail("Should have thrown a SQLException");
}
catch (SQLException expected)
{
assertEquals("sql error", expected.getMessage());
}
}
}
首先,我们用mockobject.sql包里的MockConnection建立一个 Mock Connection对象, TestableJdbcDataAccessManager把它设置进去,然后再设置Mock Statement和Mock ResultSet,并 且设置Mock ResultSet返回的期望值.然后我们就可以来进行单元测试了, 去验证数据库访问代码的返回值.注意:在tearDown()里的 connection,statement和resultSet的verify方法.这是对模拟对象添加的预期验证.比如 statement.addExpectedExecuteQuery(sql, resultSet) statement.verify()就会验证 SQL字符串是不是就是不做任何修改地传递过来的那个,执行结果是不是resultSet对象. connection.setExpectedCreateStatementCalls(1) connection.verify就会验证是否仅创建 了一个Statement.