您的位置:首页 > 数据库

JUnit学习笔记18---对数据库应用程序进行单元测试2

2010-02-14 21:27 477 查看
 

 
   在上次的笔记中,重构后的进一步测试还是发生了错误,进一步的研究发现,在类RowSetDynaClass实例化时调用了introspect。这样的错误说明了使用mocks一个潜在的问题:你需要对调用mock的类的实现有较深的了解。正如我们刚刚展示的,你可以通过调试发现对你mock的间接调用。还有另外的两种解决方法:获得访问源码的权限,或者是在不同的层次上进行模拟。
 在源码中发现间接调用
  获取源码通常是不可行的,而且会浪费大量的时间。采用最多的方法就是在不同的层次进行模拟了。这里需要测试的是execute方法而不是类RowSetDynaClass类。一个办法就是创建一个mock RowSetDynaClass并将它以某种方法传递给execute方法。
   在我们这个例子中看起来额外的建立两个方法(getMetaData和getColumnCount)更容易些。但是,当要给定的测试fixture变得长而复杂时,通常采用的方法就是在不同层次上进行。但使用mock时,如果待测之前需要设置的步数太多,就应当考虑重构了。
改正测试
我们改正了test case使得它支持对getMetaData和getColumnCount的调用。

package junitbook.database;

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 TestJdbcDataAccessManagerMO3 extends TestCase
{
private MockSingleRowResultSet resultSet;
private MockStatement statement;
private MockConnection2 connection;
private TestableJdbcDataAccessManager manager;
private MockResultSetMetaData resultSetMetaData;

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);
}

public void testExecuteOk() throws Exception
{
String sql = "SELECT * FROM CUSTOMER";
statement.addExpectedExecuteQuery(sql, resultSet);
String[] columnsLowercase =
new String[] {"firstname", "lastname"};
String[] columnsUppercase = 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"});

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());
}
}


3.2 用预期验证状态 
 
     经过上面的改动以后,还需要验证测试部分的断言。
验证数据库被正确的关闭

查询串是否是测试中传递的那个

PreparedStatement仅创建一次

等等,对此,我们使用预期(调用各自的verify())
添加预期

package junitbook.database;

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);        验证正在被执行的SQL就是我们传递的

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);验证仅创建了一个Statement
connection.setExpectedCloseCalls(1);验证close方法被调用了一次

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());
}
}


对错误的测试
在测试的过程中,时常会产生如下的清单。
getConnection方法可能会失败并产生一个SQLException的异常

Statement的创建可能会失败

查询的执行可能失败

    这些错误有的时候很隐晦,除了Bug报告,只能凭借经验。例如在测试数据库的时候,一个比较典型的错误就是出现异常时没有关闭数据库的连接。

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());
}
}


为了配合工作和维护代码的严密性,你需要在JdbcDataManager.java中使用try/finally语句。

package junitbook.database;

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 JdbcDataAccessManager2 implements DataAccessManager
{
private DataSource dataSource;

public JdbcDataAccessManager2() throws NamingException
{
this.dataSource = getDataSource();
}

protected DataSource getDataSource() throws NamingException
{
InitialContext context = new InitialContext();
DataSource dataSource = (DataSource) context.lookup(
"java:comp/env/jdbc/DefaultDS");
return dataSource;
}

protected Connection getConnection() throws SQLException
{
return this.dataSource.getConnection();
}

public Collection execute(String sql) throws Exception
{
ResultSet resultSet = null;
Connection connection = null;
Collection result = null;

try
{
connection = getConnection();

// For simplicity, we'll assume the SQL is a SELECT
// query
resultSet =
connection.createStatement().executeQuery(sql);

RowSetDynaClass rsdc = new RowSetDynaClass(resultSet);

result = rsdc.getRows();
}
finally
{
if (resultSet != null)
{
resultSet.close();
}
if (connection != null)
{
connection.close();
}
}

return result;
}
}


这就是隔离开数据库测试持久性代码的过程。 
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: