Mock 对象能够模拟领域对象的部分行为,并且能够检验运行结果是否和预期的一致。领域类将通过与 Mock 对象的交互,来获得一个独立的测试环境。
在模仿对象中,我们定义了四个概念:
目标对象:正在测试的对象
合作者对象:由目标对象创建或获取的对象
模仿对象:遵循模仿对象模式的合作者的子类(或实现)
特殊化对象:覆盖创建方法以返回模仿对象而不是合作者的目标的子类
一般来说,应用模仿对象的过程如下:
创建模仿对象的实例
设置模仿对象中的状态和期望值
将模仿对象作为参数来调用域代码
验证模仿对象中的一致性
那么,我们应该如何以及在哪里使用 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 AdminDO:
一个只含一个方法的 DAO AdminDAO:
一个同样只含一个方法的 AdminService 接口:
与 AdminService 相应的实现类:
下面就是 AdminServiceImpl 的测试类 TestEasyMock:
下面简要的说明一下 Mock 对象的工作过程:
1 )在 setUp() 中,通过 “adminDAOMock = createMock(AdminDAO.class);” (这里使用了 java5 中的静态导入),创建 AdminDAO 的 Mock 对象,由于 EasyMock 采用了范型技术,故创建的 Mock 对象不需要强制类型转换。然后通过 “adminService.setAdminDAO(adminDAOMock);” 设置目标对象的合作者对象。
2 )对于测试方法 “testQueryByUserName()” , (a) 处的 reset() 方法是将 Mock 对象复位,也就是重新设置 Mock 对象的状态和行为。由于此处是第一次调用 Mock 对象,可以不必使用 reset() 方法。
3 ) (b) 处 expect() 是录制 Mock 对象方法的调用,其参数就是 Mock 对象的方法,其中如果调用的方法有返回值,要通过 andReturn() 方法设置预期的返回值。
4 ) (c) 处的 replay() 是结束录制过程。 在调用 replay() 方法之前的状态, EashMock 称之为 “record 状态 ” 。该状态下, Mock 对象不具备行为(即模拟接口的实现),它仅仅记录方法的调用。在调用 replay() 后,它才以 Mock 对象预期的行为进行工作,检查预期的方法调用是否真的完成。
5 ) (d) 处的 verify() 是用于在录制和回放两个步骤完成之后进行预期和实际结果的检查。这里就是检查 adminDAOMock 是否如预期一样调用了 queryByUserName 方法。
对于上面的举例,它可能并不具有实际的价值,这里我只想抛砖引玉。在 N 层架构的 Java 程序中, Mock 对象在单元测试中正发挥着越来越重要的作用。我现在看到的是,在 Service 层与 Web 层, Mock 对象能很好的被应用。有人觉得在 Persistence 层也应该使用 Mock 对象,但就像我们所知道的,在使用 Hibernate 、 Ibatis 等 ORM 工具的情况下,我们的 Persistence 层的测试主要测试的就是那些配置文件、查询语句等(实际上是集成测试),如果还 Mock 的话,就失去了测试的意义。
在模仿对象中,我们定义了四个概念:
目标对象:正在测试的对象
合作者对象:由目标对象创建或获取的对象
模仿对象:遵循模仿对象模式的合作者的子类(或实现)
特殊化对象:覆盖创建方法以返回模仿对象而不是合作者的目标的子类
一般来说,应用模仿对象的过程如下:
创建模仿对象的实例
设置模仿对象中的状态和期望值
将模仿对象作为参数来调用域代码
验证模仿对象中的一致性
那么,我们应该如何以及在哪里使用 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 AdminDO:
public class AdminDO implements Serializable {
private static final long serialVersionUID = 6203447765048268430L;
/**
* 用户名
*/
private String userName;
/**
* 密码
*/
private String psw;
// -------------- extend attribute --------------------
// -------------- normal method -----------------------
// -------------- setter/getter -----------------------
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPsw() {
return psw;
}
public void setPsw(String psw) {
this.psw = psw;
}
}
一个只含一个方法的 DAO AdminDAO:
public interface AdminDAO {
AdminDO queryByUserName(String userName);
}
一个同样只含一个方法的 AdminService 接口:
public interface AdminService {
AdminDO queryByUserName(String userName);
}
与 AdminService 相应的实现类:
public class AdminServiceImpl implements AdminService {
private AdminDAO AdminDAO;
@Override
public AdminDO queryByUserName(String userName) {
return AdminDAO.queryByUserName(userName);
}
public void setAdminDAO(AdminDAO adminDAO) {
AdminDAO = adminDAO;
}
}
下面就是 AdminServiceImpl 的测试类 TestEasyMock:
import com.hsmonkey.weijifen.biz.dal.daointerface.AdminDAO;
import com.hsmonkey.weijifen.biz.dal.dataobject.AdminDO;
import junit.framework.TestCase;
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;
/**
* <p>标题: </p>
* <p>描述: </p>
* <p>版权: </p>
* <p>创建时间: 2016年11月19日 下午5:29:42</p>
* <p>作者:niepeng</p>
*/
public class TestEasyMock extends TestCase {
private AdminDAO adminDAOMock;
private AdminServiceImpl adminService;
@Override
protected void setUp() throws Exception {
adminDAOMock = createMock(AdminDAO.class);
adminService = new AdminServiceImpl();
adminService.setAdminDAO(adminDAOMock);
}
public void testQueryByUserName() {
AdminDO admin = new AdminDO();
String userName = "niepeng";
String psw = "123456";
admin.setUserName(userName);
admin.setPsw(psw);
reset(adminDAOMock); // a
expect(adminDAOMock.queryByUserName(userName)).andReturn(admin); //b
replay(adminDAOMock); // c
AdminDO callAdmin = adminService.queryByUserName(userName);
assertEquals(admin, callAdmin);
verify(adminDAOMock); // d
}
}
下面简要的说明一下 Mock 对象的工作过程:
1 )在 setUp() 中,通过 “adminDAOMock = createMock(AdminDAO.class);” (这里使用了 java5 中的静态导入),创建 AdminDAO 的 Mock 对象,由于 EasyMock 采用了范型技术,故创建的 Mock 对象不需要强制类型转换。然后通过 “adminService.setAdminDAO(adminDAOMock);” 设置目标对象的合作者对象。
2 )对于测试方法 “testQueryByUserName()” , (a) 处的 reset() 方法是将 Mock 对象复位,也就是重新设置 Mock 对象的状态和行为。由于此处是第一次调用 Mock 对象,可以不必使用 reset() 方法。
3 ) (b) 处 expect() 是录制 Mock 对象方法的调用,其参数就是 Mock 对象的方法,其中如果调用的方法有返回值,要通过 andReturn() 方法设置预期的返回值。
4 ) (c) 处的 replay() 是结束录制过程。 在调用 replay() 方法之前的状态, EashMock 称之为 “record 状态 ” 。该状态下, Mock 对象不具备行为(即模拟接口的实现),它仅仅记录方法的调用。在调用 replay() 后,它才以 Mock 对象预期的行为进行工作,检查预期的方法调用是否真的完成。
5 ) (d) 处的 verify() 是用于在录制和回放两个步骤完成之后进行预期和实际结果的检查。这里就是检查 adminDAOMock 是否如预期一样调用了 queryByUserName 方法。
对于上面的举例,它可能并不具有实际的价值,这里我只想抛砖引玉。在 N 层架构的 Java 程序中, Mock 对象在单元测试中正发挥着越来越重要的作用。我现在看到的是,在 Service 层与 Web 层, Mock 对象能很好的被应用。有人觉得在 Persistence 层也应该使用 Mock 对象,但就像我们所知道的,在使用 Hibernate 、 Ibatis 等 ORM 工具的情况下,我们的 Persistence 层的测试主要测试的就是那些配置文件、查询语句等(实际上是集成测试),如果还 Mock 的话,就失去了测试的意义。
扫描二维码关注公众号,回复:
3857922 查看本文章