我一直在寻找有关为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等)的连接的方法编写单元测试?
答案 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,然后在测试之后断言类的状态(以及协作者,即数据库)。
必须强调的是,当您对课程进行单元测试时,您的课程必须是唯一受测试的系统。像连接和数据源这样的项目仅仅是可以而且应该被嘲笑的合作者。某些单元测试将使用内存数据库(如H2,HSQL或Derby)进行单元测试,并使用生产等效的数据库安装进行集成和功能测试。
答案 1 :(得分:2)
尝试使用http://www.dbunit.org/intro.html。
主要思想 - 使用已知数据集创建存根数据库以运行测试并断言结果。 您需要在运行之前重新加载数据集以恢复初始状态。
答案 2 :(得分:1)
我们正在使用内存中的HSQL数据库。它非常快且符合SQL-92标准。为了使我们的PostgreSQL查询在HSQL上运行,我们使用自编写的测试SessionFactory(Hibernate)重写查询。相对于真实数据库的优点是:
答案 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.
}
}