JMock的基本使用方法

JMock是帮助创建mock对象的工具,它基于Java开发,在Java测试与开发环境中有不可比拟的优势,更重要的是,它大大简化了虚拟对象的使用。本文中,通过一个简单的测试用例来说明JMock如何帮助我们实现这种孤立测试。
我们在测试某类时,由于它要与其他类发生联系,因此往往在测试此类的代码中也将与之联系的类也一起测试了。这种测试,将使被测试的类直接依赖于其他类,一旦其他类发生改变,被测试类也随之被迫改变。更重要的是,这些其他类可能尚未经过测试,因此必须先测试这些类,才能测试被测试类。这种情况下,测试驱动开发成为空谈。而如果其他类中也引用了被测试类,我们到底先测试哪一个类?因此,在测试中,如果我们能将被测试类孤立起来,使其完全不依赖于其他类的具体实现,这样,我们就能做到测试先行,先测试哪个类,就先实现哪个类,而不管与之联系的类是否已经实现。

虚拟对象(mock object)就是为此需要而诞生的。它通过JDK中的反射机制,在运行时动态地创建虚拟对象。在测试代码中,我们可以验证这些虚拟对象是否被正确地调用了,也可以在明确的情况下,让其返回特定的假想值。而一旦有了这些虚拟对象提供的服务,被测试类就可以将虚拟对象作为其他与之联系的真实对象的替身,从而轻松地搭建起一个很完美的测试环境。

JMock是帮助创建mock对象的工具,它基于Java开发,在Java测试与开发环境中有不可比拟的优势,更重要的是,它大大简化了虚拟对象的使用。

本文中,通过一个简单的测试用例来说明JMock如何帮助我们实现这种孤立测试。有三个主要的类,User,UserDAO,及UserService。本文中,我们只需测试UserService,准备虚拟UserDAO。对于User,由于本身仅是一个过于简单的POJO(注:POJO是一个简单的普通的Java对象,它包含业务逻辑或持久逻辑等,但不是JavaBean、EntityBean等,不具有任何特殊角色和不继承或不实现任何其它Java框架的类或接口),可以不用测试。但如果你是一个完美主义者,也可以使用JMock的虚拟它。在这领域,JMock几乎无所不能。:)

User是一个POJO,用以在视图中传输数据及映射数据库。其代码如下:

package com.sarkuya.model;
public class User {
private String name;
public User() {
}

public User(String name) {
this.name = name;
}

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

UserDAO负责与数据库打交道,通过数据库保存、获取User的信息。尽管我们可以不用知道JMock如何通过JDK 的反射机制来实现孤立测试,但至少应知道,JDK的反射机制要求这些在运行时创建的动态类必须定义接口。在使用JMock的环境中,由于我们要虚拟 UserDAO,意味着UserDAO必须定义接口。代码如下:

package com.sarkuya.dao;
import com.sarkuya.model.User;
public interface UserDAO {
public void saveUser(User user);
public User getUser(Long id);
}

UserService存有UserDAO的引用,通过其对外提供应用级的服务。相应地,我们先定义了其接口(尽管在本文中,作为被测试类,UserService不需要有接口,但如果以后此类需要被虚拟,也应该带有接口,基于此原因,我们也为其定义了接口)。

package com.sarkuya.service;
import com.sarkuya.dao.UserDAO;
import com.sarkuya.model.User;
public interface UserService {
public void setUserDAO(UserDAO userDAO);

public void saveUser(User user);
public User getUser(Long id);
}

可以看到,除了setUserDAO()外,其另外的方法与UserDAO一样。这是设计模式中门面模式的典型应用,应用只通过UserService提供服务,而UserService在内部通过调用UserDAO来实现相应的功能。

根据测试先行的原则,你应该先写测试,再编写实现。这里先编写实现的原因,主要是使读者更加清楚我们接着要测试什么。由于本文是着重介绍JMock的使用,加上UserServiceImpl比较简单,因此先列出其代码如下:

package com.sarkuya.service.impl;
import com.sarkuya.dao.UserDAO;
import com.sarkuya.model.User;
import com.sarkuya.service.UserService;
public class UserServiceImpl implements UserService {
private UserDAO userDAO;

public UserServiceImpl() {
}
public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO;
}
public User getUser(Long id) { return userDAO.getUser(id);
}
public void saveUser(User user) {
userDAO.saveUser(user);
}
}

下面是UserService的测试代码:

package com.sarkuya.service;
import com.sarkuya.dao.UserDAO;
import com.sarkuya.model.User;
import com.sarkuya.service.impl.UserServiceImpl;
import junit.framework.*;
import org.jmock.Mock;
import org.jmock.MockObjectTestCase;
public class UserServiceTest extends MockObjectTestCase {
private UserService userService = new UserServiceImpl();

private Mock userDAO = null;

public UserServiceTest(String testName) {
super(testName);
}
protected void setUp() throws Exception {
userDAO = new Mock(UserDAO.class); userService.setUserDAO((UserDAO)userDAO.proxy());
}

protected void tearDown() throws Exception {
}

public static Test suite() {
TestSuite suite = new TestSuite(UserServiceTest.class);

return suite;
}
public void testGetUser() {
User fakeUser = new User("John");

userDAO.expects(once()).method("getUser").with(eq(1L)).will(returnValue(fakeUser));
User user = userService.getUser(1L);
assertNotNull(user);
assertEquals("John", user.getName());
}

public void testSaveUser() {
User fakeUser = new User("John");
userDAO.expects(once()).method("getUser").with(eq(1L)).will(returnValue(fakeUser));
User user = userService.getUser(1L);
assertEquals("John", user.getName());
userDAO.expects(once()).method("saveUser").with(same(fakeUser)); user.setName("Mike");
userService.saveUser(user);
userDAO.expects(once()).method("getUser").with(eq(1L)).will(returnValue(user));
User modifiedUser = userService.getUser(1L);
assertEquals("Mike", user.getName());
}
}

此段代码有几点应注意:

1、此测试类继承了JMock的MockObjectTestCase

2、private Mock userDAO = null;说明userDao是一个准备虚拟的对象

3、在setup()中,将userDAO.class传入Mock()后,再通过proxy()方法返回一个UserDAO的代理类实例(即虚拟对象实例),并赋值于userService

4、在testGetUser()方法中,如果我们先将第一行及第二行代码屏蔽掉,可以看出,这是一个真实环境下的测试代码。先获取一个User,然后确认其非空值,再确认其姓名为“John”。此时,在真实环境下,这段代码要测试成功的前提必须是UserDAO已经连接到了数据库,然后返回一个User后传给UserService。
但问题是,到目前为止,且不说UserDAO还未经历连接数据库这一系列繁琐而痛苦的过程,我们甚至还未实现 UserDAO的接口!那么,为何加上第一行及第二行代码后就可以了呢?这正是JMock的威力所在。先实例化一个测试用的fakeUser,然后通过一系列的指令,在第二行代码中告诉JMock应该如何“做假”。尽管这句代码很长,我们可作如下理解:
1) userDAO.expects(once()):我们期望userDAO的某方法被执行一次,如果此方法未被执行,或者执行了二次以上,测试就不会通过
2) method("getUser"):这个期望被执行一次的方法名为userDAO.getUser()
3) with(eq(1L)):执行getUser()方法时,确认其传入的参数值为“1L”
4) will(returnValue(fakeUser)):上述条件均满足后,返回一个虚假的对象,即我们前面实例化的fakeUser
总体来说,当设定好第二行语句后,JMock就在后台监控着,确保userDAO.getUser()必须,且只被执行一次,且参数“1L”已经正确地传给了此方法,一旦这些条件被满足,就返回fakeUser。
而在第三行,User user = userService.getUser(1L)将触发所有这些条件,作为奖励,它接受了奖品fakeUser并赋值于user对象。而下面第四行及第五行均对此user对象进行测试,不通过才怪。

5) testSaveUser()方法中的原理类似。其思路是,将id为“1”的user从数据库中取出,将其名改为“Mike”,再存回数据库,然后再从数据库中取出此user,确保其名字已被改变。
第五行userDAO.expects(once()).method("saveUser").with(same(fakeUser))比较特殊。首先,with(same(fakeUser))说明,传入参数必须是fakeUser此实例,尽管我们在下面的语句中通过user.setName ("Mike"),但只是改变了其name的属性,而fakeUser的实例引用并未发生改变,因此可以满足条件。其次,其后没有.will (returnValue(fakeUser)),因为userDAO.saveUser()不需要返回任何对象或基本数据类型。
另外,当再次执行userDAO.expects()时,JMock将重设其监控条件。我们也可以通过userDAO.reset()来显式是清除监控条件。

通过以上实例代码及其说明,我们看出,用好JMock的关键是先设置监控条件,再写相应的测试语句。一旦设好监控条件后,在某段代码块执行完毕时,如果监控条件未得到满足,或是没有通过expects()再次重设条件,或通过reset()来显式是清除监控条件,测试将无法通过。

以上介绍了JMock的基本使用方法。而这种基本用法,占了全面掌握JMock所需学习的知识70%以上。关于JMock的更多细节,感兴趣的读者可以访问JMock的网站进一步学习
<think>首先,用户的问题是:“我想查找使用JMock在调用方法时伪造异常的方法 请问JMock调用方法时伪造异常的方法”。请注意,用户说的是“JMock”,但在引用中提到了“JMockit”,这可能是个错误或混淆。引用[4]的标题是“Jmockit使用详解之Mocking”,所以很可能用户指的是JMOCKit,而不是原始的JMockJMock是另一个似Mockito,但从上下文看,引用[4]是JMOCKit。用户说“JMock”,但引用是“Jmockit”,我应该澄清或假设用户指的是JMOCKit。 关键点是:用户想知道如何使用JMOCKit在调用方法时伪造异常。 现在,回顾引用: - 引用[1] 展示了如何mock私有方法使用NonStrictExpectations和invoke。 - 引用[2] 解释了mock是stub + expectation,期望方法被调用。 - 引用[3] 讨论了方法存根(stub),描述了如何返回预设值或模拟行为,包括模拟异常。 - 引用[4] 是关于JMOCKit的Mocking,提到了record-replay-verify模型和其他特性。 我需要基于这些引用生成答案。用户要求参考站内引用,所以我应该引用相关部分。 从引用[3]中:"存根(Stub)... 模拟异常、超时等边界场景。" 这暗示了模拟异常的能力。 在引用[4]中,有“Delegate自定义结果result”,可能包括抛出异常。 在JMOCKit中,模拟方法抛出异常的方法使用result字段或Delegate。 在JMOCKit的Expectations块中,你可以设置result为抛出异常。例如: ```java new Expectations() {{ mockedObject.someMethod(); result = new Exception("Expected exception"); }}; ``` 或者在record阶段设置。 用户上一次的问题是似的主题,但系统指令说参考用户上一次的问题。用户没有提供上一次的问题,所以我应该基于当前引用。 现在,结构回答: - 系统指令:使用中文回答,结构清晰,帮助用户逐步解决问题。尽量保证真实可靠。 - 必须添加引用标识:在引用段落末尾添加[^n]。 回答问题: 用户问:使用JMock在调用方法时伪造异常的方法。 但引用中是JMOCKit,所以我应该确认:用户可能意指JMOCKit。 在回答中,我应该说明JMOCKit或JMock。用户说“JMock”,但引用是“Jmockit”,所以我需要处理这个。可能用户打错了,或者JMOCKit有时被简称为JMock。在社区中,JMOCKit是独立的库。 为了准确,我会假设用户指的是JMOCKit,因为它出现在引用中。 步骤: 1. **引入主题**:解释如何在JMOCKit中模拟方法抛出异常。 2. **具体方法**:在Expectations中,使用`result`字段来抛出异常。 例如:`result = new SomeException("message");` 3. **代码示例**:基于引用[1],提供似的代码。 在引用[1]中,有使用NonStrictExpectations和result。 似地,我可以写一个测试方法伪造异常。 4. **引用相关部分**:在适当的地方添加引用标识。 例如,"在JMOCKit中,你可以通过设置result来抛出异常 [^4]." 5. **生成相关问题**:系统指令要求生成相关问题。 在末尾添加§§相关问题§§部分。 完整回答结构: - 开头解释方法。 - 提供代码示例。 - 引用支持。 - 相关问题。 代码示例: 假设我们有一个: ```java public class SomeService { public String getData() throws IOException { // 实际实现 } } ``` 在测试中,mock getData()抛出IOException。 ```java @Test public void testException() throws Exception { SomeService mockService = new MockUp<SomeService>() { @Mock public String getData() throws IOException { throw new IOException("Mocked exception"); } }.getMockInstance(); // 或者使用Expectations new Expectations() {{ mockService.getData(); result = new IOException("Expected exception"); }}; // 测试调用 try { mockService.getData(); fail("Exception expected"); } catch (IOException e) { assertEquals("Expected exception", e.getMessage()); } } ``` JMOCKit有多种方式:使用MockUp或Expectations。Expectations更常见。 在引用[1]中,使用的是NonStrictExpectations和invoke,但对于公共方法,可以直接mock。 简化代码。 在Expectations块中设置result抛出异常。 最终代码: ```java @Test public void testMockException() throws Exception { final ClassToMock obj = new ClassToMock(); new Expectations(obj) {{ obj.someMethod(anyInt); // 期望调用someMethod result = new SomeException("Mocked error"); // 伪造异常 }}; try { obj.someMethod(1); // 调用方法 fail("Exception should be thrown"); } catch (SomeException e) { assertEquals("Mocked error", e.getMessage()); } new Verifications() {{ // 验证调用 obj.someMethod(1); times = 1; }}; } ``` 参考引用[1]和[2]。 现在,生成相关问题: 1. 如何在JMOCKit中mock静态方法抛出异常? 2. JMOCKit如何模拟超时或延迟行为? 3. JMOCKit与其他mock框架(如Mockito)在异常处理上的比较? 保持相关。 回答中引用段落末尾添加标识。 例如:"在单元测试中,模拟异常可以帮助验证错误处理逻辑 [^3]." [^3] 是关于stub模拟异常。 </think>以下是针对您查询“使用JMock在调用方法时伪造异常的方法”的详细解答。根据您提供的站内引用和上下文(如引用[4]关于JMOCKit的Mocking机制),您可能指的是**JMOCKit**框架(而非原始JMock库),因为引用中明确提到了“Jmockit”。在JMOCKit中,伪造异常的核心是通过**Expectations区块**设置`result`字段或使用`Delegate`来模拟方法抛出指定异常。以下是逐步指南,结合代码示例和引用依据: ### 1. **JMOCKit伪造异常的基本方法** 在JMOCKit的`record-replay-verify`模型中: - **Record阶段**:在`Expectations`块中定义方法调用和期望结果,通过`result = new ExceptionType()`直接指定抛出的异常。 - **Replay阶段**:执行测试代码,触发被mock的方法。 - **Verify阶段**:使用`Verifications`验证调用是否符合预期。 例如,假设有一个`UserService`,其方法`fetchUser()`可能抛出`IOException`: ```java public class UserService { public String fetchUser(int id) throws IOException { // 实际实现(如数据库查询) return "User" + id; } } ``` 测试中模拟`fetchUser()`抛出异常: ```java import mockit.Expectations; import mockit.Verifications; import mockit.Mocked; import org.junit.Test; import java.io.IOException; public class UserServiceTest { @Mocked UserService userService; // 使用@Mocked注解创建mock实例 @Test(expected = IOException.class) // JUnit预期异常 public void testFetchUserWithException() throws IOException { // Record阶段:设置期望和伪造异常 new Expectations() {{ userService.fetchUser(anyInt); // 匹配任意整数参数 result = new IOException("Mocked network error"); // 伪造异常 }}; // Replay阶段:调用方法(预期抛出异常) userService.fetchUser(1); } // 可选:验证调用次数 @Test public void testVerifyExceptionCall() { new Expectations() {{ userService.fetchUser(anyInt); result = new IOException("Error"); }}; try { userService.fetchUser(1); } catch (IOException e) { // 处理异常 } // Verify阶段:确保方法被调用 new Verifications() {{ userService.fetchUser(1); times = 1; // 验证调用一次 }}; } } ``` - **关键点**: - `result = new IOException(...)`:直接在Expectations中指定抛出的异常实例。 - `anyInt`:参数匹配器,表示任意整数参数(似引用[4]中的“any匹配”)。 - `@Test(expected = ...)`:JUnit注解简化异常断言。 - 如果需模拟私有方法异常,可结合`invoke`(如引用[1]): `this.invoke(obj, "privateMethodName", args); result = new Exception();` [^1]。 ### 2. **高级用法:使用Delegate动态生成异常** 对于复杂场景(如根据参数动态抛异常),可用`Delegate`接口: ```java @Test public void testDelegateException() throws IOException { new Expectations() {{ userService.fetchUser(anyInt); result = new Delegate() { void delegate(int id) throws IOException { if (id < 0) { throw new IllegalArgumentException("Invalid ID"); } else { throw new IOException("Custom error"); } } }; }}; try { userService.fetchUser(-1); // 抛出IllegalArgumentException } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } } ``` - **优势**:`Delegate`允许在运行时基于参数自定义异常行为,增强测试灵活性 [^4]。 ### 3. **注意事项** - **异常型匹配**:伪造的异常必须与被mock方法声明的异常兼容(否则测试失败)。 - **静态方法**:若模拟静态方法使用`this.invoke(null, "methodName")`并设置`result`(参考引用[1]) [^1]。 - **部分Mocking**:如果只需Mock部分方法,用`new Expectations(instance)`而非`@Mocked`,避免全mock。 - **框架对比**:JMOCKit的异常模拟更强大(支持静态/私有方法),而Mockito等需额外配置 [^2][^4]。 在单元测试中,Mock异常能有效验证错误处理逻辑(如服务降级),确保系统鲁棒性 [^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值