边城愚人

如果我不在边城,我一定是在前往边城的路上。

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  31 随笔 :: 0 文章 :: 96 评论 :: 0 Trackbacks

Mock 对象能够模拟领域对象的部分行为,并且能够检验运行结果是否和预期的一致。领域类将通过与 Mock 对象的交互,来获得一个独立的测试环境(引自《 精通 Spring——Java 轻量级架构开发实践 》。

在模仿对象中,我们定义了四个概念:

1 目标对象:正在测试的对象

2 合作者对象:由目标对象创建或获取的对象

3 模仿对象:遵循模仿对象模式的合作者的子类(或实现)

4 特殊化对象:覆盖创建方法以返回模仿对象而不是合作者的目标的子类

一般来说,应用模仿对象的过程如下:

1 )创建模仿对象的实例

2 )设置模仿对象中的状态和期望值

3 )将模仿对象作为参数来调用域代码

4 )验证模仿对象中的一致性

那么,我们应该如何以及在哪里使用 Mock 对象呢?一般来说,对于目标对象中的合作者对象,在测试时如果其状态或行为的实现严重地依赖外部资源(比如数据持久化中的 DAO ,比如负责发送电子邮件的类),或者团队并行开发时,目标对象的合作者对象并没有实现(比如 J2EE 中,横向分工时,负责 Action 的调用 Service ,负责 Service 调用 DAO 时,相应的 Service DAO 没有实现),这时我们就需要模仿这些类。其实,在做 J2EE 时,传统的 N 层架构中,我们都是面向接口编程的,我们定义了 DAO 接口,我们定义了 Service 接口,这样做的优点就是我们在测试时可以构造实现接口的 Mock 类。这里不得不提依赖注入,通过依赖注入,我们才能在测试时 set Mock 对象。这也说明,为了方便测试,我们不得不一步一步 重构代码,而模式就在重构中自然地产生了。

对于 Mock 对象,我们可以根据 合作者接口(或者是类) 实现具体的 Mock 类,这样的 Mock 类实际上是 Stub 。有些情况下, Stub 是必要的。但对于诸如 DAO Service ,我们只关心在给定参数的情况下,调用的方法能够返回预期的值,我们根本不关心其内部实现,这时候如果使用 Stub 的话就会产生不必要的代码。我们需要的就是能 动态地生成 Mock 对象而不需要编写它们的工具, EasyMock 就是这样的工具。

EasyMock 是一个 Mock 对象的类库,现在的版本是 2.0 ,这个版本只支持 Mock 接口,如果需要 Mock 类,需要下载它的扩展包。

下面通过一个具体的例子说明一下 Mock 对象的使用,我写的例子就是测试 Service 类中的一个方法, Mock 的对象是 DAO

首先是一个简单的实体 bean Account:

package  easymocktest.domain;

import
 org.apache.commons.lang.builder.ToStringBuilder;
import
 org.apache.commons.lang.builder.ToStringStyle;

public   class  Account  {

    
private  Long id;
    
private  String name;
    
private  String pwd;
    
    
public  Long getId()  {
        
return  id;
    }

    
public   void  setId(Long id)  {
        
this .id  =  id;
    }

    
public  String getName()  {
        
return  name;
    }

    
public   void  setName(String name)  {
        
this .name  =  name;
    }

    
public  String getPwd()  {
        
return  pwd;
    }

    
public   void  setPwd(String pwd)  {
        
this .pwd  =  pwd;
    }

    
/**
     * 
@see  java.lang.Object#toString()
     
*/

    
public  String toString()  {
        
return   new  ToStringBuilder( this , ToStringStyle.MULTI_LINE_STYLE)
                .append(
" name " this .name).append( " pwd " this .pwd).append( " id " ,
                        
this .id).toString();
    }

    
}

      

    一个只含一个方法的 DAO AccountDAO:

package  easymocktest.dao;

import  easymocktest.domain. *
;
public   interface  AccountDAO  {
    
public  Account getByNameAndPwd(String name,String pwd);
}

    

 

   一个同样只含一个方法的 AccountService 接口:

 

package  easymocktest.service;

import  easymocktest.domain. *
;
public   interface  AccountService  {
    
public  Account getAccount(String name,String pwd);
}

   与 AccountService 相应的实现类:

package  easymocktest.service.impl;

import
 easymocktest.service.AccountService;
import  easymocktest.dao. *
;
import
 easymocktest.domain.Account;
public   class  AccountServiceImpl  implements  AccountService  {

    
private  AccountDAO accountDAO;

    
public  AccountDAO getAccountDAO()  {
        
return  accountDAO;
    }


    
public   void  setAccountDAO(AccountDAO accountDAO)  {
        
this .accountDAO  =  accountDAO;
    }


    
public  Account getAccount(String name, String pwd)  {
        
return   this .accountDAO.getByNameAndPwd(name, pwd);
    }


}


   这里我没有实现 AccountDAO 接口,对于 Mock 测试来说,这也是不需要的。下面就是 AccountServiceImpl 的测试类 AccountServiceTest
   

package  easymocktest.service;

import  junit.framework. *
;
import  easymocktest.dao. *
;
import  easymocktest.domain. *
;
import  easymocktest.service.impl. *
;
import   static
 org.easymock.EasyMock.createMock;
import   static
 org.easymock.EasyMock.replay;
import   static
 org.easymock.EasyMock.reset;
import   static
 org.easymock.EasyMock.verify;
import   static
 org.easymock.EasyMock.expect;
public   class  AccountServiceTest  extends  TestCase {

    
private  AccountDAO accountDAOMock;
    
private  AccountServiceImpl accountService;
    
    @Override
    
protected   void  setUp()  throws  Exception  {
        accountDAOMock 
=  createMock(AccountDAO. class );
        accountService 
=   new  AccountServiceImpl();
        accountService.setAccountDAO(accountDAOMock);
    }

    
    
public   void  testGetAccount() {
        String name 
=   " kafka " ;
        String pwd 
=   " 0102 " ;
        Account a 
=   new  Account();
        a.setName(name);
        a.setPwd(pwd);
        a.setId(
new  Long( 10 ));
        reset(accountDAOMock);
// (a)
        expect(accountDAOMock.getByNameAndPwd(name, pwd)).andReturn(a); // (b)
        replay(accountDAOMock); // (c)
        Account b  =  accountService.getAccount(name, pwd);
        assertEquals(a, b);
        verify(accountDAOMock);
// (d)
    }

}

   

下面简要的说明一下 Mock 对象的工作过程:

1 )在 setUp() 中,通过 “accountDAOMock = createMock(AccountDAO.class);” (这里使用了 java5 中的静态导入),创建 AccountDAO Mock 对象,由于 EasyMock 采用了范型技术,故创建的 Mock 对象不需要强制类型转换。然后通过 “accountService.setAccountDAO(accountDAOMock);” 设置目标对象的合作者对象。

2 )对于测试方法 “testGetAccount()” (a) 处的 reset() 方法是将 Mock 对象复位,也就是重新设置 Mock 对象的状态和行为。由于此处是第一次调用 Mock 对象,可以不必使用 reset() 方法。

3 (b) expect() 是录制 Mock 对象方法的调用,其参数就是 Mock 对象的方法,其中如果调用的方法有返回值,要通过 andReturn() 方法设置预期的返回值。

4 (c) 处的 replay() 是结束录制过程。 在调用 replay() 方法之前的状态, EashMock 称之为 “record 状态 。该状态下, Mock 对象不具备行为(即模拟接口的实现),它仅仅记录方法的调用。在调用 replay() 后,它才以 Mock 对象预期的行为进行工作,检查预期的方法调用是否真的完成。

5 (d) 处的 verify() 是用于在录制和回放两个步骤完成之后进行预期和实际结果的检查。这里就是检查 accountDAOMock 是否如预期一样调用了 getByNameAndPwd 方法。

 

对于上面的举例,它可能并不具有实际的价值,这里我只想抛砖引玉。在 N 层架构的 Java 程序中, Mock 对象在单元测试中正发挥着越来越重要的作用。我现在看到的是,在 Service 层与 Web 层, Mock 对象能很好的被应用。有人觉得在 Persistence 层也应该使用 Mock 对象,但就像我们所知道的,在使用 Hibernate Ibatis ORM 工具的情况下,我们的 Persistence 层的测试主要测试的就是那些配置文件、查询语句等(实际上是集成测试),如果还 Mock 的话,就失去了测试的意义。

   对于 Mock 的更多的信息,你可以访问Mock Objects,在本文写作的过程中,参考了使用模仿对象进行单元测试

等文章,一并感谢。

posted on 2007-04-26 08:35 kafka0102 阅读(4013) 评论(1)  编辑  收藏 所属分类: TDD

评论

# re: EasyMock使用手记 2009-05-07 15:04 josdoc
Java开源文档
www.josdoc.com
转载了您的文章,若有异议请告知,谢谢!  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: