我们如何为涉及与DB连接的方法编写单元测试?

时间:2011-07-17 12:10:45

标签: java unit-testing

我一直在寻找有关为Web方法编写单元测试的查询,这些方法实际上与数据库通信并返回一些值。

比如说我有一个名为“StudentInfoService”的Web服务。 该网络服务提供API“getStudentInfo(studentid)”

以下是一些示例代码段

public class StudentInfoService
{
    public StudentInfo getStudentInfo(long studentId) {
            //Communicates with DB and creates
            // StudentInfo object with necessary information
            // and returns it to the caller.
    }
}

我们如何为此方法getStudentInfo实际编写单元测试? 一般来说,我们如何为涉及与资源(数据库,文件,JNDI等)的连接的方法编写单元测试?

4 个答案:

答案 0 :(得分:4)

首先,示例中的类StudentInfoService不可测试,或者至少不容易。这是一个非常简单的原因 - 没有办法将数据库连接对象传递给类,至少不是在您列出的方法中。

使类可测试需要您以下列方式构建类:

public class StudentInfoService
{
    private Connection conn;

    public StudentInfoService(Connection conn)
    {
        this.conn = conn;
    }

    public StudentInfo getStudentInfo(long studentId) {
            //Uses the conn object to communicate with DB and creates
            // StudentInfo object with necessary information
            // and returns it to the caller.
    }
}

上面的代码允许通过构造函数注入依赖项。如果更合适,您可以使用setter注入而不是构造函数注入,但它通常不适用于DAO / Repository类,因为在没有连接的情况下不能将该类视为完全形成。

依赖注入将允许您的测试用例创建与数据库的连接(该数据库是您所测试的类/系统的协作者),而不是让类/系统本身创建协作者对象。简单来说,您正在解耦从类中建立数据库连接的机制。如果您的类以前正在查找JNDI数据源然后创建连接,那么它将是不可测试的,除非您使用Apache Cactus或类似Arquillian之类的框架将其部署到容器中,或者如果您使用嵌入式容器。通过隔离从类创建连接的问题,您现在可以自由地在类外部的单元测试中创建连接,并根据需要将它们提供给类,从而允许您在Java SE环境中运行测试。

这将使您能够使用面向数据库的单元测试框架,如DbUnit,这将允许您在每次测试之前将数据库设置为已知状态,然后将连接传递给{{1} } class,然后在测试之后断言类的状态(以及协作者,即数据库)。

必须强调的是,当您对课程进行单元测试时,您的课程必须是唯一受测试的系统。像连接和数据源这样的项目仅仅是可以而且应该被嘲笑的合作者。某些单元测试将使用内存数据库(如H2HSQLDerby)进行单元测试,并使用生产等效的数据库安装进行集成和功能测试。

答案 1 :(得分:2)

尝试使用http://www.dbunit.org/intro.html

主要思想 - 使用已知数据集创建存根数据库以运行测试并断言结果。 您需要在运行之前重新加载数据集以恢复初始状态。

答案 2 :(得分:1)

我们正在使用内存中的HSQL数据库。它非常快且符合SQL-92标准。为了使我们的PostgreSQL查询在HSQL上运行,我们使用自编写的测试SessionFactory(Hibernate)重写查询。相对于真实数据库的优点是:

  1. 快得多,这对单元测试很重要
  2. 不需要配置
  3. 随处运行,包括我们的持续集成服务器

答案 3 :(得分:0)

使用“遗留代码”时,如果没有一定程度的重构,可能很难编写单元测试。在编写对象时,我尝试遵守SOLID。作为SOLID的一部分,“D”代表依赖性反转。

遗留代码的问题在于您可能已经拥有许多使用StudentInfoService的无参数构造函数的客户端,这可能会使添加构造函数的Connection conn参数变得困难。

我建议的通常不是最佳做法,因为您在生产系统中公开了测试代码,但它有时最适合使用遗留代码。

public class StudentInfoService {
    private final Connection conn;

    /**
     * This no arg constructor will automatically establish a connection for you. This
     *  will remain around to support legacy code that depends on a no arg constructor.
     */
    public StudentInfoService() throws Exception {
        conn = new ConcreteConnectionObject( ... );
    }

    /**
     * This constructor may be used by your unit tests (or new code).
     */
    public StudentInfoService( Connection conn ) {
        this.conn = conn;
    }

    public StudentInfo getStudentInfo() {
        // this method will need to be slightly refactored to use
        // the class variable "conn" instead of establishing its own connection inline.
    }

}