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

Unit-testing database applications      无论软件开发规模,依赖关系都是软件开发的关键问题……去除程序中的重复部分,也就去除了依赖性。

                                                                                                                                                                                    ——Kent Beck

本章内容

  • 用mock objects在隔离数据库的情况下进行单元测试
  • 用Cactus和DbUnit进行集成测试
  • 改进集成测试的性能

    本章的目的就是告诉我们,不仅对数据库访问代码进行单元测试是可行的,而且还有很多的解决方案,在探究完对数据库进行单元测试的种种方法之后,将启示一些准则,帮助你决定你的特定应用程序到底选用哪种方法!

    1.对数据库进行单元测试的介绍

(图片备用链接)

    o_image_thumb_634010667672088750[5]

在前面的三个笔记中,我们描述了如何对JSP和filter进行单元测试,而今天针对的部分就是图中黄色区域了!

这里我们重点学习如何对JDBC组件进行单元测试。

这里假设你的数据库访问代码已经清晰的与你的业务逻辑代码区分了。在此处分离关注焦点是个很好的做法,在不改变其他部分代码的情况下,使得测试变得简单了!

  1. 对业务逻辑的单元测试
  2. 对数据库访问的单元测试
  3. 数据库集成单元测试

2.隔离开数据库测试业务逻辑

   这里我们的目标是对不包含数据库访问代码的业务逻辑代码进行单元测试。尽管这种测试本质上说不是数据库测试,但它却是独立的测试那些难以测试的数据库代码的一个很好的策略。幸运的是,如果你将数据库的访问层和业务逻辑层分开的话,这项工作将变得非常的简单。让我们看看这意味着什么。AdminServlet的定义如下:

package junitbook.database;

import java.util.Collection;

import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

public class AdminServlet extends HttpServlet
{
    public void doGet(HttpServletRequest request,HttpServletResponse response)
    throws ServletException
    {
    }
    public String getCommand(HttpServletRequest request )
    throws ServletException
    {
    }
    public Collection executeCommand(String command) 
        throws Exception
    {
    }
}

在这种方法下程序执行的逻辑图如下:

image

   接下来的诀窍就是为数据库访问层建立一个接口,有了接口,你就可以用mock objects策略来对数据库访问层进行单元测试。

   实现数据库访问层的接口

我们将此接口称为DataAccessManager,并把它的实现称为JdbcDataAccessManager。

package junitbook.database;

import java.util.Collection;

public interface DataAccessManager
{
    Collection execute(String sql) throws Exception;
}

既然已经有了数据访问的接口,你需要重构类AdminServlet以使用该接口并实例化DataAccessManager的JdbcDataAccessManager的实现。

下面的代码展示了重构的结果:

package junitbook.database;

import java.util.Collection;

import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

public class AdminServlet extends HttpServlet
{
    // [...]

    private DataAccessManager dataManager;
    public void init() throws ServletException
    {
        super.init();
        
        try
        {
            setDataAccessManager(new JdbcDataAccessManager());
        }
        catch (NamingException e)
        {
            throw new ServletException(e);
        }
    }
    
    public Collection executeCommand(String command) 
        throws Exception
    {
        return this.dataManager.execute(command);
    }
}

现在为AdminServlet类的一个方法写一个单元测试就很简单了。你所要做的就是建立一个DataAccessManager的mock object实现。唯一需要技巧的地方就是决定如何将mock实例传给AdminServlet类以使AdminServlet类使用mock实现,而不是真实的JdbcDataAccessManager的实现。

      建立一个模拟数据库接口
      你可以采用多种策略来将一个DataAccessManager的mock 传给AdminServlet:

  • 创建一个参数形式接受DataAccessManager接口的构造函数
  • 创建一个setter方法(setDataAccessManager(DataAccessManager manager))
  • 派生AdminServlet类,重载executeCommand()
  • 在web.xml文件中定义一个作为AdminServlet初始化参数的类名,使数据访问管理器实现成为你的应用程序的参数

    对于上面的方法,最好的方法就是使用setter方法了。所以,再次的将AdminServlet重构如下:

package junitbook.database;

import java.util.Collection;

import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

public class AdminServlet extends HttpServlet
{
    // [...]

    private DataAccessManager dataManager;

    public DataAccessManager getDataAccessManager()
    {
        return this.dataManager;
    }
    
    public void setDataAccessManager(DataAccessManager manager)
    {
        this.dataManager = manager;
    }
            
    public void init() throws ServletException
    {
        super.init();
        
        try
        {
            setDataAccessManager(new JdbcDataAccessManager());
        }
        catch (NamingException e)
        {
            throw new ServletException(e);
        }
    }
    
    public Collection executeCommand(String command) 
        throws Exception
    {
        return this.dataManager.execute(command);
    }
}

 

  模拟数据库接口层 

使用test case如何创建并使用mock DataAccessManager类来对AdminServlet类进行单元测试呢?下面就是使用DynaMock API写的mock例子。

package junitbook.database;

import java.util.ArrayList;

import com.mockobjects.dynamic.Mock;
import com.mockobjects.dynamic.C;

import junit.framework.TestCase;

public class TestAdminServletDynaMock extends TestCase
{
    public void testSomething() throws Exception
    {
        Mock mockManager = new Mock(DataAccessManager.class);
        DataAccessManager manager = 
            (DataAccessManager) mockManager.proxy();

        mockManager.expectAndReturn("execute", C.ANY_ARGS, 
            new ArrayList());

        AdminServlet servlet = new AdminServlet();
        servlet.setDataAccessManager(manager);
            
        // Call the method to test here. For example:
        // manager.doGet(request, response)
        
        // [...]
    }
}

首先用DynaMock API创建一个DataAccessManager mock object,接下来当调用execute方法时你让该mock返回一个空的ArrayList。接着你要setDataAccessManager方法建立一个mock管理器。
3.隔离开数据库测试持久性代码

前面的章节将业务层和数据访问层进行隔离测试,接下来,就将对数据访问层进行测试。

首先看一下将要被测试的代码

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 JdbcDataAccessManager implements DataAccessManager
{
    private DataSource dataSource;
    
    public JdbcDataAccessManager() throws NamingException
    {
        this.dataSource = getDataSource();
    }

    protected DataSource getDataSource() throws NamingException
    {
        InitialContext context = new InitialContext();
        DataSource dataSource = 
            (DataSource) context.lookup("java:/DefaultDS");
        return 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方法很简单。这种简单源于BeanUtils包的使用。BeanUtils提供了一个RowSetDynaClass,它封装了一个ResultSet并将数据库各列映射到bean属性中。然后你就可以将各列作为属性用DynaBean API来访问了。

  类RowSetDynaClass自动将ResultSet各列拷贝到dyna bean的属性中,这就使得你可以在一结束初始化RowSetDynaClass对象后就关闭与数据库的链接。

      测试execute方法

      让我们为execute方法写单元测试,就是对所有的JDBC的调用提供mock。

       首先,将一个mock Connection对象传递给JdbcDataAccessManager类。在这里,我们创建一个封装类。你可以将getConnection方法定义为保护成员,接着创建一个派生自JdbcDataAccessManager 的新类TestableJdbcDataAccessManager。并添加setter方法,这样就绕过了DataSource而获得连接。

package junitbook.database;

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

   创建第一个测试

  现在我们有了自己的方式来编写execute方法,所以,开始为它编写第一个测试程序。对于任何使用mocks的测试来说,困难的地方在于找到那些需要模拟的方法。换句话说,为了提供模拟的响应你必须准确的理解API的哪些方法将被调用。通常,你可以尝试犯些错误,接着测试运行,一步步重构。

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.MockStatement;

import junit.framework.TestCase;

public class TestJdbcDataAccessManagerMO1 extends TestCase
{
    private MockStatement statement;
    private MockConnection2 connection;
    private TestableJdbcDataAccessManager manager;

    protected void setUp() throws Exception
    {
        statement = new MockStatement();    创建Statement和Connection mock objects
        connection = new MockConnection2();
        connection.setupStatement(statement);  让Connection mock 返回mock Statement对象

        manager = new TestableJdbcDataAccessManager();  实例化该封装类,并调用setConnection()方法来传递mock Connection对象
        manager.setConnection(connection);
    }

    public void testExecuteOk() throws Exception
    {
        String sql = "SELECT * FROM CUSTOMER";

        Collection result = manager.execute(sql); 调用方法进行单元测试

        Iterator beans = result.iterator();  通过返回的Collection来判断结果
        assertTrue(beans.hasNext());
        DynaBean bean1 = (DynaBean) beans.next();
        assertEquals("John", bean1.get("firstname"));
        assertEquals("Doe", bean1.get("lastname"));
        assertTrue(!beans.hasNext());
    }
}

这个程序并没有结束,你会发现一个错误,你还没有告诉但executeQuery方法被调用时mock Statement应该返回些什么!

改进该测试

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.MockSingleRowResultSet;
import com.mockobjects.sql.MockStatement;

import junit.framework.TestCase;

public class TestJdbcDataAccessManagerMO2 extends TestCase
{
    private MockSingleRowResultSet resultSet;
    private MockStatement statement;
    private MockConnection2 connection;
    private TestableJdbcDataAccessManager manager;
            
    protected void setUp() throws Exception
    {
        resultSet = new MockSingleRowResultSet();

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

这里我们使用了MockSingeRowResultSet实现。MockObjects.com提供了两种实现,MockSingleRowResultSet和MockMultiRowResultSet。顾名思义

:第一种是用来模拟只有一行的ResultSet,而第二种方法模拟具有多行的情况。

该测试同样失败了。。。但是,我们取得了很多的进步,这里怎样改进在下一笔记中解决。   

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JUnit-BOM(Bill of Materials)是JUnit团队为JUnit 5框架提供的一个特殊模块。它的目的是简化JUnit 5的依赖管理。通过添加JUnit-BOM到你的项目中,你可以使用JUnit 5的所有核心模块和扩展模块,而无需单独指定每个模块的版本。 要使用JUnit-BOM,你需要在你的项目的构建文件中添加一个依赖项。对于Maven项目,可以在`<dependencies>`标签中添加以下内容: ``` <dependency> <groupId>org.junit</groupId> <artifactId>junit-bom</artifactId> <version>5.x.x</version> <!-- 替换为你想要使用的JUnit 5版本 --> <type>pom</type> <scope>import</scope> </dependency> ``` 对于Gradle项目,可以在`dependencies`块中添加以下内容: ``` dependencies { testImplementation platform('org.junit:junit-bom:5.x.x') // 替换为你想要使用的JUnit 5版本 testImplementation 'org.junit.jupiter:junit-jupiter-api' // 添加其他所需的JUnit 5模块 } ``` 添加JUnit-BOM之后,你可以使用JUnit 5的各种功能,例如编写和运行JUnit单元测试。你可以使用`@Test`注解来标记测试方法,并使用JUnit提供的各种断言和注解来编写强大的单元测试。 通过引用JUnit-BOM,你可以确保你的项目使用与JUnit 5兼容的版本,并且可以轻松管理依赖关系。这使得在不同的项目中使用JUnit 5变得更加简单和一致。 JUnit-BOM的详细信息可以在JUnit官方文档中找到。 JUnit是一个用于编写和运行可重复自动化测试的Java单元测试框架。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值