准备测试数据并检测运行结果
在这节里,我们将着手解决上面所提出的两个问题,在测试用例中准备测试数据并到数据库中检测业务执行结果的正确性。
准备测试数据
相比于在测试方法中直接访问预定的数据记录,在测试方法执行前通过程序准备一些测试数据,然后在此基础上运行测试方法是比较好的策略,因为后者不需要对数据库的状态做假设。在 TestContext 中,你可以通过使用 JUnit 4.4 的 @Before 注解达到这个目的,请看下面的代码:
清单5. 为测试方法准备数据
package com.baobaotao.service;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.
AbstractTransactionalJUnit4SpringContextTests;
import com.baobaotao.dao.UserDao;
import com.baobaotao.domain.User;
@ContextConfiguration
public class TestUserService
extends AbstractTransactionalJUnit4SpringContextTests {
@Autowired
private UserService userService;
@Autowired
private UserDao userDao;
private int userId;
@Before //① 准备测试数据
public void prepareTestData() {
final String sql = "insert into t_user(user_name,password) values('tom','1234')";
simpleJdbcTemplate.update(sql);
KeyHolder keyHolder = new GeneratedKeyHolder();
simpleJdbcTemplate.getJdbcOperations().update(
new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection conn)
throws SQLException {
PreparedStatement ps = conn.prepareStatement(sql);
return ps;
}
}, keyHolder);
userId = keyHolder.getKey().intValue();//①-1 记录测试数据的id
}
@Test
public void handleUserLogin(){
User user = userDao.getUserById(userId); //② 获取测试数据
user.setLastIp("127.0.0.1");
Date now = new Date();
user.setLastVisit(now.getTime());
userService.handleUserLogin(user);
}
}
JUnit 4.4 允许通过注解指定某些方法在测试方法执行前后进行调用,即是 @Before 和 @After 注解。在 Spring TestContext 中,标注 @Before 和 @After 的方法会在测试用例中每个测试方法运行前后执行,并和测试方法运行于同一个事务中。在 清单 5 中 ① 处,我们给 prepareTestData() 标注上了 @Before 注解,在该方法中准备一些测试数据,以供 TestUserService 中所有测试方法使用(这里仅有一个 handleUserLogin() 测试方法)。由于测试方法运行后,整个事务会被回滚,在 prepareTestData() 中插入的测试数据也不会持久化到数据库中,因此我们无须手工删除这条记录。
标注 @Before 或 @After 注解的方法和测试方法运行在同一个事务中,但有时我们希望在测试方法的事务开始之前或完成之后执行某些方法以便获取数据库现场的一些情况。这时,可以使用 Spring TestContext 的 @BeforeTransaction 和 @AfterTransaction 注解来达到目录(这两个注解位于 org.springframework.test.context.transaction 包中)。
虽然大多数业务方法都会访问数据库,但也并非所有需要测试的业务方法都需要和数据库打交道。而在默认情况下,继承于 AbstractTransactionalJUnit4SpringContextTests 测试用例的所有测试方法都将工作于事务环境下,你可以显式地通过 @NotTransactional 注解,让测试方法不工作于事务环境下。
prepareTestData() 方法中使用到了 simpleJdbcTemplate 对象访问操作数据库,该对象在 AbstractTransactionalJUnit4SpringContextTests 抽象类中定义,只要 Spring 容器有配置数据源,simpleJdbcTemplate 就会被自动创建。同时该抽象类中还拥有一个 Spring 容器引用:applicationContext,你可以借助该成员变量访问 Spring 容器,执行获取 Bean,发布事件等操作。
此外,AbstractTransactionalJUnit4SpringContextTests 还提供了若干个访问数据库的便捷方法,说明如下:
protected int countRowsInTable(String tableName) :计算数据表的记录数。
protected int deleteFromTables(String... names):删除表中的记录,可以指定多张表。
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError):执行 SQL 脚本文件,在脚本文件中,其格式必须一个 SQL 语句一行。
在测试方法 handleUserLogin() 的 ② 处,我们通过 userDao 获取 prepareTestData() 添加的测试数据,测试方法在测试数据的基础上执行业务逻辑。使用这种测试方式后,在任何情况下运行 TestUserService 都不会发生业务逻辑之外的问题。