编写针对 RDBMS 的 DAO 集成测试
在这一节,编写和运行一个集成测试,根据数据库测试员工信息应用程序。
测试 EmployeeService 的 EmployeeDAO 实现
现在还剩下的唯一问题是:如何和在什么时候调用 Spring 2 引擎,它怎么知道该如何使用 dwspring2-service.xml 配置文件?
看一下 EmployeeServiceIntegrationTest.java 中的集成测试源代码,答案就一目了然了。这个集成测试针对实际的 RDBMS 测试 EmployeeService 的 EmployeeDAO 实现。请参阅清单 12 中的代码片断:
清单 12. EmployeeServiceIntegrationTest 中的集成测试(第 1 部分)package com.ibm.dw.spring2;
import java.util.Date;
...
import org.springframework.test.jpa.AbstractJpaTests;
public class EmployeeServiceIntegrationTest extends AbstractJpaTests {
private EmployeeService employeeService;
private long JoeSmithId = 99999;
public void setEmployeeService(EmployeeService employeeService) {
this.employeeService = employeeService;
}
protected String[] getConfigLocations() {
return new String[] { "classpath:/com/ibm/dw/spring2/dwspring2-service.xml" };
}
这套集成测试是在 Spring 2 库的 AbstractJpaTests 类的帮助下编写的。通过实现清单 12 中强调的 getConfigLocations() 方法,可以提供一个或多个需要由 Spring 2 引擎解析的 bean 配置文件。可以有多个配置文件,因为分离后端和用户界面 bean 配置文件是一种常见实践。
清单 12 中的 setEmployeeService() 是依赖性注入的示例。当 Spring 2 引擎装入这个 EmployeeServiceIntegrationTest 类时(派生自 AbstractJpaTests),它发现一个没有填充的依赖项 —— 类型为 EmployeeService 的属性。引擎就查找 dwspring2-service.xml 文件,查找配置好的类型为 EmployeeService 的bean,并通过 setEmployeeService() 方法注入。
按类型自动连接
您可能注意到,在 dwspring2-service.xml 文件中,对于 employeeService 的注入缺少明确的注入指令。实际上,这个注入是自动发生的。在 Spring 的术语中,这叫作自动连接。
AbstractJpaTests 基类派生自 AbstractDependencyInjectionSpringContextTests 类。 AbstractDependencyInjectionSpringContextTests 通过把 Spring 的按类型自动连接特性设为默认,简化了测试。如果在应用程序的上下文(在这个示例中,由 dwspring2-service.xml 文件配置)中发现相同类型的 bean,那么它的子类(EmployeeServiceIntegrationTest 就是这样一个子类)的任何依赖项(公共属性)就被自动注入。
集成测试设置
AbstractJpaTests 针对测试的一个有用特性,就是在事务中执行测试,然后在测试后回滚所有效果的能力。这切实地加快了测试,因为在每次测试运行期间,不再需要删除和重建数据了。
清单 13 显示了执行每个测试的初始设计的代码。这个设置代码用三个 Employee 和它们的相关 Addresse 填充数据库。必须在与每个测试相同的事务内执行这个代码。否则,就不会看到注入的数据,因为事务总会被回滚。要在相同事务中执行设置,要覆盖 onSetUpInTransaction() 方法,如清单 13 所示:
清单 13. EmployeeServiceIntegrationTest 中的集成测试(第 2 部分)protected void onSetUpInTransaction() throws Exception {
Employee emp1 = new Employee("0001", "Joe", "R","Smith",
"4853", "Engineer", 3, 'M',
20000.00, 0.00, 0.00,
new Address(10, "Walker Street")
, new Date(), new Date());
Employee emp2 = new Employee("0002", "John","T","Lockheed",
"4333", "Sales", 2, 'M',
40000.00, 0.00, 5000.00,
new Address(20, "Walker Street")
, new Date(), new Date());
Employee emp3 = new Employee("0003", "Mary","M","Johnson",
"4383", "Admin", 3, 'F',
60000.00, 0.00, 390.00,
new Address(123, "Booth Ave")
, new Date(), new Date());
employeeService.save(emp1);
employeeService.save(emp2);
employeeService.save(emp3);
JoeSmithId = emp1.getEmpid();
}
请注意,通过基于 JPA 的 employeeService 创建并保持 Employee 实例有多简单。因为 save() 方法调用 JPA 的 persist() 操作,而且操作会从 Employee 级联到 Address 对象(在 Employee POJO 的 JPA 注释指定这个),所以也可以依靠 JPA 在地址表中创建新记录。
各测试每次执行时,都会执行 onSetUpInTransaction() 中的设置代码。这确保了在每次测试之前,都有三个员工。
作为测试示例,清单 14 显示了 EmployeeDAO 的 update() 方法的测试方法 testModifyEmployee():
清单 14. EmployeeServiceIntegrationTest 的集成测试(第 3 部分)
public void testModifyEmployee() {
String oldLastName = "Lockheed";
String newLastName = "Williams";
Employee emp = employeeService
.findByEmployeeLastName(oldLastName)。get(0);
emp.setLastName(newLastName);
Employee emp2 = employeeService.update(emp);
assertEquals(newLastName, emp2.getLastName());
List<Employee> results = employeeService
.findByEmployeeLastName(oldLastName);
assertEquals(0, results.size());
results = results = employeeService
.findByEmployeeLastName(newLastName);
assertEquals(1, results.size());
}
清单 14 中的测试用例 testModifyEmployee() 用 EmployeeService 的 update()把员工“John Lockheed”的姓氏从“Lockheed”改成“Williams”。然后它调用 findByEmployeeLastName("Williams"),检验最后有一个员工“Williams”。它还检验没有姓氏为“Lockheed”的员工存在。
EmployeeServiceIntegrationTest 中还有其他许多测试。可以研究这些代码查看如何利用 EmployeeService 实现上的方法操纵实际数据(请参阅 下载)。
接下来,设置根据 RDBMS 运行这些集成测试的环境。
下载和安装 JPA 引用实现
JPA 是 EJB 3.0 规范的一部分。EJB 3.0 又是 Java EE 5 规范的核心组件。因为这点,JPA 的开源引用实现成为了 GlassFish Java EE 5 引用服务器的一部分。
如果还没有做,那么请立即下载和安装 JPA 实现来执行集成测试(请参阅 参考资料)。
准备类路径
要在代码中访问 JPA 功能,在构建和运行时类路径中必须有 JPA 引用实现:
1. 在 Eclipse 中,在导航器面板中右击项目名称,并选择 Properties。
2. 选择 Java Build Path,然后在对话框中,选择 Libraries 选项卡。
3. 单击 Add External JARs 按钮,并确保包含了 JPA 下载的 toplink-essentials.jar。
要成功地编译和运行集成测试,在构建和运行时类路径中必须有依赖的库。表 3 显示了在构建和运行时类路径中必须有的 JAR 文件。可以在 Eclipse 中设置这些路径:
表 3. 类路径中必须有的开源库
| JAR 文件 | 原始下载 |
| commons-logging.jar | spring-framework-2.x-with-dependencies.zip |
| db2cc.jar | IBM DB 2 Express-C 发布 |
| db2cc_licence_cu.jar | IBM DB2 Express-C 发布版 |
| hsqldb.jar | spring-framework-2.x-with-dependencies.zip |
| junit.jar | spring-framework-2.x-with-dependencies.zip |
| log4j-1.x.xx.jar | spring-framework-2.x-with-dependencies.zip |
| persistence.jar | spring-framework-2.x-with-dependencies.zip |
| spring.jar | spring-framework-2.x-with-dependencies.zip |
| spring-core.jar | spring-framework-2.x-with-dependencies.zip |
| spring-jpa.jar | spring-framework-2.x-with-dependencies.zip |
| spring-mock.jar | spring-framework-2.x-with-dependencies.zip |
| toplink-essential.jar | JPA 引用实现下载 |
包含 persistence.xml 文件
虽然在这个 Spring JPA 集成场景中, persistence.xml 文件没有提供配置信息,但 JPA 规范要求这个文件。
persistence.xml 文件是持久性单元 的说明。在 JPA 术语中,持久性单元包含 EntityManagerFactory (和相关的配置信息),它创建的 EntityManagers 和这些 EntityManager 管理的类(以及这些类的元数据,以注释或 XML 形式)。
在这个示例中,persistence.xml 文件非常简单,如清单 15 所示:
清单 15. META-INF/persistence.xml 文件
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="dwSpring2Jpa" transaction-type="RESOURCE_LOCAL"/>
</persistence>
在某些厂商的配置中,persistence.xml 文件可以包含相关的说明信息,但是 Spring/JPA 集成中没有。只需要确定具有一份 META-INF/persistence.xml 的副本即可。
运行 Spring 集成测试
构建路径配置之后,即可运行集成测试。
要在 Eclipse 中运行集成测试,请在 Navigator 面板中选中 EmployeeServiceIntegrationTest.java 文件,然后右击选中 Run As -> JUnit Test。这就开始了集成测试。请记住这个测试是针对一个内存中的数据库运行的,非常快。在执行期间,应当在 Eclipse 的控制台窗口看到表创建、SQL 插入和查询的日志消息。请参阅图 7 查看集成测试的运行示例:
图 7. Eclipse 中运行的集成测试
把数据源从内存中的数据库切换到 DB2 Express-C
一般来说,在使用 Spring DAO 和 DAO 设计模式时,对应用程序代码来说,数据源是完全封装并隐藏的。实际上,示例中的代码完全独立于使用的关系数据库,而且独立于使用的 JPA 厂商。这种灵活性允许您在执行得快的数据库(例如内存中的数据库)上测试应用程序代码,然后把同样的代码部署到健壮和可伸缩的商业级数据库(例如 IBM DB2 Express-C)上。
要在不同的数据库之间切换,只需要修改 dwspring2-service.xml bean 描述符文件。清单 16 显示了从 HSQLDB 切换到 IBM DB2 Express-C 需要的修改(用黑体强调):
清单 16. 把数据源更改为 DB2 Express-C
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="entityManagerFactory" class=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
<property name="databasePlatform"
value="oracle.toplink.essentials.platform.database.DB2Platform"/>
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
</property>
</bean>
<bean id="dataSource" class=
"org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.ibm.db2.jcc.DB2Driver"/>
<property name="url" value="jdbc:db2://192.168.23.36:50000/dwspring"/>
<property name="username" value="bill" />
<property name="password" value="lotus123" />
</bean>
<bean id="transactionManager" class=
"org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
The modifications highlighted 清单 16 中突出的修改假设在 192.168.23.36 的端口 50000 (默认的)上有一个 DB2 Express-C 实例。需要创建叫作 dwspring 的数据库,并向应用程序用户(在清单 16 中是 bill;请把它改成自己的特定用户)提供完整访问。
现在再次运行集成测试。测试连接到 DB2 Express-C 数据库,创建表,并执行全部测试。这次的执行稍微慢了一些 —— 因为测试要跨网络执行,还要使用磁盘上的数据存储——您会注意到在执行中有些小变化。图 8 显示了在 Eclipse 中针对 DB2 Express-C 的典型运行:
图 8. 针对 DB2 Express-C 运行集成测试
应用程序的数据层现在已经完成并经过了完整测试。这一层可以用于各种不同的用户界面。例如,可以在上面放置一个命令行接口层,或者创建使用它的 GUI 胖客户。在用户界面和数据访问层之间的解耦非常重要,因为它使您能够轻松重新设计和独立地应用经过测试的数据层代码。
对于这个练习,要使用 Spring MVC 为应用程序创建基于 Web 的用户界面。
fig07.jpg fig08.jpg 2008-03-05_115422.gif