科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道对J2EE中的DAO组件编写单元测试

对J2EE中的DAO组件编写单元测试

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

单元测试作为保证软件质量及重构的基础,早已获得广大开发人员的认可。

作者:廖雪峰 来源:天极论坛整理 2007年10月13日

关键字:

  • 评论
  • 分享微博
  • 分享邮件

在本页阅读全文(共4页)

下面,我们开始对DAO组件编写单元测试。前面提到了HSQLDB这一小巧的纯Java数据库。HSQLDB除了提供完整的JDBC驱动以及事务支持外,HSQLDB还提供了进程外模式(与普通数据库类似)和进程内模式(In-Process),以及文件和内存两种存储模式。我们将HSQLDB设定为进程内模式及仅使用内存存储,这样,在运行JUnit测试时,可以直接在测试代码中启动HSQLDB。测试完毕后,由于测试数据并没有保存在文件上,因此,不必清理数据库。

  此外,为了执行批量测试,在每个独立的DAO单元测试运行前,我们都执行一个初始化脚本,重新建立所有的表。该初始化脚本是通过HibernateTool自动生成的,稍后我们还会讨论。下图是单元测试的执行顺序:
对DAO编写单元测试 图-2 

  在编写测试类之前,我们首先准备了一个TransactionCallback抽象类,该类通过Template模式将DAO调用代码通过事务包装起来:

 public abstract class TransactionCallback {
public final Object execute() throws Exception {
Transaction tx = HibernateUtil.getCurrentSession().beginTransaction();
try {
Object r = doInTransaction();
tx.commit();
return r;
}
catch(Exception e) {
tx.rollback();
throw e;
}
}
// 模板方法:
protected abstract Object doInTransaction() throws Exception;
}

  其原理是使用JDK提供的动态代理。由于JDK的动态代理只能对接口代理,因此,要求DAO组件必须实现接口。如果只有具体的实现类,则只能考虑CGLIB之类的第三方库,在此我们不作更多讨论。

  下面我们需要编写DatabaseFixture,负责启动HSQLDB数据库,并在@Before方法中初始化数据库表。该DatabaseFixture可以在所有的DAO组件的单元测试类中复用:

 public class DatabaseFixture {
private static Server server = null; // 持有HSQLDB的实例
private static final String DATABASE_NAME = "javaeedev"; // 数据库名称
private static final String SCHEMA_FILE = "schema.sql"; // 数据库初始化脚本
private static final List<String> initSqls = new ArrayList<String>();  @BeforeClass // 启动HSQLDB数据库
public static void startDatabase() throws Exception {
if(server!=null)
return;
server = new Server();
server.setDatabaseName(0, DATABASE_NAME);
server.setDatabasePath(0, "mem:" + DATABASE_NAME);
server.setSilent(true);
server.start();
try {
Class.forName("org.hsqldb.jdbcDriver");
}
catch(ClassNotFoundException cnfe) {
throw new RuntimeException(cnfe);
}
LineNumberReader reader = null;
try {
reader = new LineNumberReader(new    InputStreamReader(DatabaseFixture.class.getClassLoader().getResourceAsStream(SCHEMA_FILE)));
for(;;) {
String line = reader.readLine();
if(line==null) break;
// 将text类型的字段改为varchar(2000),因为HSQLDB不支持text:
line = line.trim().replace(" text ", " varchar(2000) ").replace("  text,", " varchar(2000),");
if(!line.equals(""))
initSqls.add(line);
}
}
catch(IOException e) {
throw new RuntimeException(e);
}
finally {
if(reader!=null) {
try { reader.close(); } catch(IOException e) {}
}
}
}  @Before // 执行初始化脚本
public void initTables() {
for(String sql : initSqls) {
executeSQL(sql);
}
} static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:hsqldb:mem:" + DATABASE_NAME, "sa", "");
} static void close(Statement stmt) {
if(stmt!=null) {
try {
stmt.close();
}
catch(SQLException e) {}
}
} static void close(Connection conn) {
if(conn!=null) {
try {
conn.close();
}
catch(SQLException e) {}
}
} static void executeSQL(String sql) {
Connection conn = null;
Statement stmt = null;
try {
conn = getConnection();
boolean autoCommit = conn.getAutoCommit();
conn.setAutoCommit(true);
stmt = conn.createStatement();
stmt.execute(sql);
conn.setAutoCommit(autoCommit);
}
catch(SQLException e) {
log.warn("Execute failed: " + sql + "\nException: " + e.getMessage());
}
finally {
close(stmt);
close(conn);
}
} public static Object createProxy(final Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
return new TransactionCallback() {
@Override
protected Object doInTransaction() throws Exception {
return method.invoke(target, args);
}
}.execute();
}
}
);
}
}

  注意DatabaseFixture的createProxy()方法,它将一个普通的DAO对象包装为在事务范围内执行的代理对象,即对于一个普通的DAO对象的方法调用前后,自动地开启事务并根据异常情况提交或回滚事务。

  下面是UserDaoImpl的单元测试类:

 public class UserDaoImplTest extends DatabaseFixture {
private UserDao userDao = new UserDaoImpl();
private UserDao proxy = (UserDao)createProxy(userDao);  @Test
public void testQueryUser() {
User user = newUser("test");
proxy.createUser(user);
User t = proxy.queryUser("test");
assertEquals(user.getEmail(), t.getEmail());
}
}

  注意到UserDaoImplTest持有两个UserDao引用,userDao是普通的UserDaoImpl对象,而proxy则是将userDao进行了事务封装的对象。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章