JUnit学习笔记18---对数据库应用程序进行单元测试2
来源:互联网 发布:linux主机名修改不重启 编辑:程序博客网 时间:2024/06/14 13:58
在上次的笔记中,重构后的进一步测试还是发生了错误,进一步的研究发现,在类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()); }}
经过上面的改动以后,还需要验证测试部分的断言。
- 验证数据库被正确的关闭
- 查询串是否是测试中传递的那个
- 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; }}
这就是隔离开数据库测试持久性代码的过程。
- JUnit学习笔记18---对数据库应用程序进行单元测试2
- JUnit学习笔记18---对数据库应用程序进行单元测试2
- JUnit学习笔记17---对数据库应用程序进行单元测试1
- JUnit学习笔记19---对数据库应用程序进行单元测试3
- JUnit学习笔记20---对数据库应用程序进行单元测试4
- JUnit学习笔记20---对数据库应用程序进行单元测试4
- JUnit学习笔记17---对数据库应用程序进行单元测试1
- JUnit学习笔记13---对servlet和filter进行单元测试2
- JUnit学习笔记15---对JSP和taglib进行单元测试2
- JUnit学习笔记15---对JSP和taglib进行单元测试2
- JUnit学习笔记13---对servlet和filter进行单元测试2
- JUnit学习笔记14---对JSP和taglib进行单元测试1
- JUnit学习笔记14---对JSP和taglib进行单元测试1
- Junit 对应用进行单元测试
- junit对spring进行单元测试
- 学习使用junit进行单元测试,
- Androidx学习笔记(18)-- junit单元测试
- Android学习笔记---对应用进行单元测试
- About Cursor
- 休假是很轻松的事
- HibernateDaoSupport的源码
- hibernatedaosupport的使用
- 恭祝每一位朋友虎年大吉!
- JUnit学习笔记18---对数据库应用程序进行单元测试2
- 新年快乐
- HTTP Method小结(GET,POST.etc)
- 2010-02-14
- 短信SMS
- POJ 2243 ZOJ 1091 UVaOJ 439 Knight moves(BFS广度优先搜索)
- 拯救MySQL 签署请愿书
- POJ1459解题报告
- UML基本概念