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

在上次的笔记中,重构后的进一步测试还是发生了错误,进一步的研究发现,在类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 用预期验证状态 

 

     经过上面的改动以后,还需要验证测试部分的断言。

  1. 验证数据库被正确的关闭
  2. 查询串是否是测试中传递的那个
  3. 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;        
    }
}

就是隔离开数据库测试持久性代码的过程。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值