当前位置:首页 > 全部子站 > IT > 思科认证

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

来源:长理培训发布时间:2017-12-22 10:23:32

 百度广告

  单元测试作为保证软件质量及重构的基础,早已获得广大开发人员的认可。单元测试是一种细粒度的测试,越来越多的开发人员在提交功能模块时也同时提交相应的单元测试。对于大多数开发人员来讲,编写单元测试已经成为开发过程中必须的流程和最佳实践。 

  对普通的逻辑组件编写单元测试是一件容易的事情,由于逻辑组件通常只需要内存资源,因此,设置好输入输出即可编写有效的单元测试。对于稍微复杂一点的组件,例如Servlet,我们可以自行编写模拟对象,以便模拟HttpRequest和HttpResponse等对象,或者,使用EasyMock之类的动态模拟库,可以对任意接口实现相应的模拟对象,从而对依赖接口的组件进行有效的单元测试。

  在J2EE开发中,对DAO组件编写单元测试往往是一件非常复杂的任务。和其他组件不通,DAO组件通常依赖于底层数据库,以及JDBC接口或者某个ORM框架(如Hibernate),对DAO组件的测试往往还需引入事务,这更增加了编写单元测试的复杂性。虽然使用EasyMock也可以模拟出任意的JDBC接口对象,或者ORM框架的主要接口,但其复杂性往往非常高,需要编写大量的模拟代码,且代码复用度很低,甚至不如直接在真实的数据库环境下测试。不过,使用真实数据库环境也有一个明显的弊端,我们需要准备数据库环境,准备初始数据,并且每次运行单元测试后,其数据库现有的数据将直接影响到下一次测试,难以实现"即时运行,反复运行"单元测试的良好实践。

针对DAO组件给出一种较为合适的单元测试的编写策略。在JavaEE开发网的开发过程中,为了对DAO组件进行有效的单元测试,我们采用HSQLDB这一小巧的纯Java数据库作为测试时期的数据库环境,配合Ant,实现了自动生成数据库脚本,测试前自动初始化数据库,极大地简化了DAO组件的单元测试的编写。

在Java领域,JUnit作为第一个单元测试框架已经获得了最广泛的应用,无可争议地成为Java领域单元测试的标准框架。以最新的JUnit 4版本为例,演示如何创建对DAO组件的单元测试用例。

  JavaEEdev的持久层使用Hibernate 3.2,底层数据库为MySQL。为了演示如何对DAO进行单元测试,我们将其简化为一个DAOTest工程:

  由于将Hibernate的Transaction绑定在Thread上,因此,HibernateUtil类负责初始化SessionFactory以及获取当前的session: 
public class HibernateUtil { 
  static { 
  sessionFactory =  new AnnotationConfiguration() 
  .buildsessionFactory(); 
  catch(Exception e) { 
  } 
  public static Session  getCurrentsession() { 
  } 
  public static void createEntity(Object entity); 
  public static List queryForList(String hql, Object params); 
  @Table(name="T_USER") 
  public static final  String REGEX_USERNAME = "[a-z0-9][a-z0-9""-]{1,18}[a-z0-9]"; 
  public static final  String REGEX_EMAIL = "([0-9a-zA-Z]([-.""w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-""w]*[0-9a-zA-Z]"".)+[a-zA-Z]{2,9})";   
  private String  password;   // MD5口令  
  private String  email;    // 电子邮件  
  private long  createdDate;  // 创建时间  
  public User() {}   
  this.username = username; 
  this.admin = admin; 
  @Id 
  @Pattern(regex=REGEX_USERNAME) 
  public void setUsername(String  username) { this.username = username; }  
  @Pattern(regex=REGEX_PASSWORD) 
  public void  setPassword(String password) { this.password = password; }  
  @Pattern(regex=REGEX_EMAIL) 
  public void  setEmail(String email) { this.email = email; }   
  public boolean getAdmin()  { return admin; } 
  @Column(nullable=false,  updatable=false) 
  public void  setCreatedDate(long createdDate) { this.createdDate = createdDate; }  
  public int  getEmailValidation() { return emailValidation; } 
  @Column(nullable=false) 
  public void  setLockDate(long lockDate) { this.lockDate = lockDate; } 
  public boolean  getEmailValidated() { return emailValidation==0; }   
  public boolean  getLocked() { 
0 && lockDate>System.currentTimeMillis(); 
  } 
  @Table(name="T_PWDT") 
  private String id; 
  private String ticket; 
  @Id 
  @GeneratedValue(generator="system-uuid") 
  public String getId() {  return id; } 
  @ManyToOne 
  public User getUser() {  return user; } 
  @Column(nullable=false,  updatable=false, length=32) 
  public void  setTicket(String ticket) { this.ticket = ticket; } 
  public long  getCreatedDate() { return createdDate; } 
  } 
  User  queryForSignOn(String username); 
  void createUser(User  user); 
  boolean  updateEmailValidation(String username, int ticket); 
  boolean  updatePassword(String username, String oldPassword, String newPassword);
  boolean  updateResetPassword(User user, String ticket, String password);
  void updateUnlock(User  user); 
    UserDaoImpl是其实现类: public class UserDaoImpl implements UserDao { 
  User user =  queryUser(username); 
  throw new  LockException(user.getLockDate()); 
  } 
  return (User)  HibernateUtil.query(User.class, username); 
public void  createUser(User user) { 
  HibernateUtil.createEntity(user); 
  // 其余方法略  
  } 
  try { 
  tx.commit(); 
  catch(Exception e) { 
  throw e; 
下面,我们开始对DAO组件编写单元测试。前面提到了HSQLDB这一小巧的纯Java数据库。HSQLDB除了提供完整的JDBC驱动以及事务支持外,HSQLDB还提供了进程外模式(与普通数据库类似)和进程内模式(In-Process),以及文件和内存两种存储模式。我们将HSQLDB设定为进程内模式及仅使用内存存储,这样,在运行JUnit测试时,可以直接在测试代码中启动HSQLDB。测试完毕后,由于测试数据并没有保存在文件上,因此,不必清理数据库。

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

   在编写测试类之前,我们首先准备了一个TransactionCallback抽象类,该类通过Template模式将DAO调用代码通过事务包装起来: public abstract class TransactionCallback { 
  Transaction tx =  HibernateUtil.getCurrentsession().beginTransaction(); 
  Object r =  doInTransaction(); 
  return r; 
  catch(Exception e) { 
  throw e; 
  } 
  protected abstract Object  doInTransaction() throws Exception; 
    其原理是使用JDK提供的动态代理。由于JDK的动态代理只能对接口代理,因此,要求DAO组件必须实现接口。如果只有具体的实现类,则只能考虑CGLIB之类的第三方库,在此我们不作更多讨论。

  下面我们需要编写DatabaseFixture,负责启动HSQLDB数据库,并在@Before方法中初始化数据库表。该DatabaseFixture可以在所有的DAO组件的单元测试类中复用: public class DatabaseFixture { 
  private static final  String DATABASE_NAME = "Javaeedev"; // 数据库名称  
  private static final  List initSqls = new ArrayList();
  public static void  startDatabase() throws Exception { 
  return; 
  server.setDatabaseName(0,  DATABASE_NAME); 
  server.setSilent(true); 
  try { 
  } 
  throw new  RuntimeException(cnfe); 
  LineNumberReader  reader = null; 
  reader = new  LineNumberReader(new     InputStreamReader(DatabaseFixture.class.getClassLoader().getResourceAsStream(SCHEMA_FILE)));
  String line =  reader.readLine(); 
  // 将text类型的字段改为varchar(2000),因为HSQLDB不支持text:  
  if(!line.equals("")) 
  } 
  catch(IOException e)  { 
  } 
  if(reader!=null)  { 
  } 
  } 
  public void initTables()  { 
  executeSQL(sql); 
  } 
  return  DriverManager.getConnection("jdbc:hsqldb:mem:" + DATABASE_NAME,  "sa", "");
static void  close(Statement stmt) { 
  try { 
  } 
  } 
static void  close(Connection conn) { 
  try { 
  } 
  } 
static void  executeSQL(String sql) { 
  Statement stmt =  null; 
  conn =  getConnection(); 
  conn.setAutoCommit(true); 
  stmt.execute(sql); 
  } 
  log.warn("Execute failed: " + sql + ""nException: "  + e.getMessage());
  finally { 
  close(conn); 
  } 
  return  Proxy.newProxyInstance( 
  target.getClass().getInterfaces(), 
  public  Object invoke(Object proxy, final Method method, final Object args) throws  Throwable {
  @Override 
  return method.invoke(target, args); 
  }.execute(); 
  } 
  } 
    注意DatabaseFixture的createProxy()方法,它将一个普通的DAO对象包装为在事务范围内执行的代理对象,即对于一个普通的DAO对象的方法调用前后,自动地开启事务并根据异常情况提交或回滚事务。

  下面是UserDaoImpl的单元测试类: public class UserDaoImplTest extends DatabaseFixture { 
  private UserDao proxy =  (UserDao)createProxy(userDao); 
  public void  testQueryUser() { 
  proxy.createUser(user); 
  assertEquals(user.getEmail(), t.getEmail()); 
  } 

  由于UserDaoImplTest从DatabaseFixture继承,因此,@Before方法在每个@Test方法调用前自动调用,这样,每个@Test方法执行前,数据库都是一个经过初始化的"干净"的表。

   对于普通的测试,如UserDao.queryUser()方法,直接调用proxy.queryUser()即可在事务内执行查询,获得返回结果。

  对于异常测试,例如期待一个ResourceNotFoundException,就不能直接调用proxy.queryUser()方法,否则,将得到一个UndeclaredThrowableException:

  这是因为通过反射调用抛出的异常被代理类包装为UndeclaredThrowableException,因此,对于异常测试,只能使用原始的userDao对象配合TransactionCallback实现: @Test(expected=ResourceNotFoundException.class) 
  new TransactionCallback()  { 
  userDao.queryUser("nonexist"); 
  } 
  } 
  
   
   
  
   
   
   

  利用HSQLDB,我们已经成功地简化了对DAO组件进行单元测试。我发现这种方式能够找出许多常见的bug:HQL语句的语法错误,包括SQL关键字和实体类属性的错误拼写,反复运行单元测试就可以不断地修复许多这类错误,而不需要等到通过Web页面请求而调用DAO时才发现问题; 传入了不一致或者顺序错误的HQL参数数组,导致Hibernate在运行期报错; 一些逻辑错误,包括不允许的null属性(常常由于忘记设置实体类的属性),更新实体时引发的数据逻辑状态不一致。

   总之,单元测试需要根据被测试类的实际情况,编写最简单最有效的测试用例。旨在给出一种编写DAO组件单元测试的有效方法。

责编:罗莉

发表评论(共0条评论)
请自觉遵守互联网相关政策法规,评论内容只代表网友观点,发表审核后显示!

国家电网校园招聘考试直播课程通关班

  • 讲师:刘萍萍 / 谢楠
  • 课时:160h
  • 价格 4580

特色双名师解密新课程高频考点,送国家电网教材讲义,助力一次通关

配套通关班送国网在线题库一套

课程专业名称
讲师
课时
查看课程

国家电网招聘考试录播视频课程

  • 讲师:崔莹莹 / 刘萍萍
  • 课时:180h
  • 价格 3580

特色解密新课程高频考点,免费学习,助力一次通关

配套全套国网视频课程免费学习

课程专业名称
讲师
课时
查看课程
在线题库
面授课程更多>>
图书商城更多>>
在线报名
  • 报考专业:
    *(必填)
  • 姓名:
    *(必填)
  • 手机号码:
    *(必填)
返回顶部