学习如何使用 仿真器来创建测试模拟,记录和重放预期,并验证模拟实例的方法调用。我们将用JUnit4和JUnit 5设置EasyMock,两者都是。
目录
1.EasyMock的依赖性
将Maven资源库中最新版本的easymock纳入项目。
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>4.3</version>
<scope>test</scope>
</dependency>
2.使用EasyMock的测试步骤
EasyMock框架使用java.lang.reflect.Proxy对象创建模拟对象。当我们创建一个模拟对象时,在测试执行过程中,代理对象会取代真实对象的位置。代理对象从我们创建模拟对象时传递的接口或类中获得其字段和方法。
一个典型的使用EasyMock的测试有四个阶段:创建模拟、期望、重放和验证。
- 创建mock。使用
EasyMock.mock()来创建目标类的mock,这些类的行为我们要委托给代理对象。一般来说,我们模拟与外部系统交互的类或不应该成为测试代码一部分的类。 - 记录期望值。使用
EasyMock.expect()来记录来自模拟对象的期望。这些期望包括用某些参数模拟一个方法,被调用的方法的返回值和该方法被调用的次数。 - 重放。
EasyMock.replay()方法使Mock对象可用。在*'重放'*模式下,当测试调用一个记录的方法时,那么mock将返回上一步的记录结果。 - 验证:
EasyMock.verify(),验证在测试执行过程中,所有的预期都按照记录执行,并且没有对mock进行意外调用。
我们将在第4节看到如何执行所有这些步骤。
3.用JUnit设置EasyMock
在进一步行动之前,重要的是要了解我们需要遵循不同的方法来运行测试,基础的JUnit版本是4或5。所以你可以根据你的项目要求选择以下解决方案之一。
以下解决方案是用来处理测试类中的**@Mock和@TestSubject**注解的。如果我们不使用这些注解,那么我们可以跳过使用以下解决方案。
3.1.使用JUnit 4
传统的JUnit 4使用EasyMockRunner类来运行测试。注意,这个运行器只适用于JUnit 4.5或更高版本。
@RunWith(EasyMockRunner.class)
public class EasyMockTests {
}
在JUnit 4中,我们也可以使用EasyMockRule来代替EasyMockRunner,效果相同。
public class EasyMockTests {
@Rule
public EasyMockRule mockRule = new EasyMockRule(this);
}
3.2.使用JUnit 5
在JUnit 5中,不能再使用规则了。新的JUnit 5使用EasyMockExtension类来运行测试。从EasyMock 4.1开始,EasyMock开箱就带有这个JUnit 5扩展。
@ExtendWith(EasyMockExtension.class)
public class EasyMockTests {
}
4.EasyMock演示
让我们通过一个例子来了解easymock的所有步骤。我们将首先对一些类和依赖关系进行模拟,然后我们将为它写一个测试。
4.1.被测系统
我们有一个RecordService 类,可以用来在后台数据库中保存Record数据。RecordService依赖于RecordDao ,与数据库进行交互,SequenceGenerator ,获得下一个有效的序列号作为Recordid。
@Data
@NoArgsConstructor
public class Record {
public Record(String name) {
this.name = name;
}
private long id;
private String name;
}
@Log
public class SequenceGenerator {
private long value = 1;
public long getNext() {
log.info("Get Next Id in SequenceGenerator");
return value++;
}
}
@Log
public class RecordDao {
public Record saveRecord(Record record) {
log.info("Saving Record in RecordDao");
return record;
}
}
@Log
public class RecordService {
private final RecordDao dao;
private final SequenceGenerator generator;
public RecordService(SequenceGenerator generator, RecordDao dao) {
this.generator = generator;
this.dao = dao;
}
public Record saveRecord(Record record) {
log.info("Saving Record in RecordService");
record.setId(generator.getNext());
return dao.saveRecord(record);
}
}
4.2.一个简单的测试
在给定的测试中,我们正在测试RecordService.saveRecord() 方法。该服务依赖于RecordDao和SequenceGenerator。Dao与数据库交互,序列生成器也与数据库交互以获取下一个记录ID。我们需要模拟这两个依赖关系,因为它们不在这个测试案例的范围内。
//Prepare mocks
RecordDao mockDao = EasyMock.mock(RecordDao.class);
SequenceGenerator mockGenerator = EasyMock.mock(SequenceGenerator.class);
下一步是在两个模拟中记录预期。在下面几行中,我们要在两个mock中设置方法调用的期望值,如果方法被调用,要返回什么值,以及该方法要被调用多少次。
我们可以使用灵活的匹配器,如anyObject(), isA(), notNull()等来编写与一些参数匹配的期望值。但是我们必须从结果匹配器中返回一个具体的值,如andReturn()或andThrow()方法。
使用once(),times(exactCount),times(min, max),atLeastOnce()和anyTimes() 提到调用次数。
Record record = new Record();
record.setName("Test Record");
expect(mockGenerator.getNext()).andReturn(100L).once();
expect(mockDao.saveRecord(EasyMock.anyObject(Record.class)))
.andReturn(record).once()
为了让测试执行进入重放模式,我们可以使用重放模拟,要么一个一个地重放,要么在一个重放调用中结合所有模拟。
replay(mockGenerator, mockDao);
//or
replay(mockGenerator);
replay(mockDao);
如果我们不想跟踪测试中的所有模拟,我们可以使用EasyMockSupport来一次性重放所有的模拟。
public class MockEasyTests {
EasyMockSupport support = new EasyMockSupport();
@Test
public void test() {
//...
support.replayAll();
//...
}
}
在重放模式下,我们在被测系统中执行操作。这将调用预期中的记录方法,并从模拟对象中返回值。
最后,我们验证模拟对象是否满足了所有的期望,并且模拟对象上没有发生意外的调用。*verify()的语法与replay()*方法相似。使用以下选项之一来触发对mock的验证。
verify(mockGenerator, mockDao);
//or
verify(mockGenerator);
verify(mockDao);
//or
EasyMockSupport support = new EasyMockSupport();
support.verifyAll();
一个涉及上述所有步骤的完整测试案例的例子如下。
public class EasyMockTests {
@Test
public void whenSaveCorrectRecord_ItSavedSuccessfully() {
//Prepare mocks
RecordDao mockDao = EasyMock.mock(RecordDao.class);
SequenceGenerator mockGenerator = EasyMock.mock(SequenceGenerator.class);
Record record = new Record();
record.setName("Test Record");
//Set expectations
//expect(mockGenerator.getNext()).andReturn(100L).once();
mockGenerator.getNext();
expectLastCall().andReturn((long) 100);
expect(mockDao.saveRecord(EasyMock.anyObject(Record.class)))
.andReturn(record).once();
//Replay
replay(mockGenerator, mockDao);
//Test and assertions
RecordService service = new RecordService(mockGenerator, mockDao);
Record savedRecord = service.saveRecord(record);
assertEquals("Test Record", savedRecord.getName());
assertEquals(100L, savedRecord.getId());
//Verify
verify(mockGenerator, mockDao);
}
}
4.3.一个使用注解的测试
前面的例子直接用mock() 方法来创建mock,然后将mock注入到RecordService类中。我们可以使用**@Mock和@TestSubject**注解来做这个声明性的工作。
请注意,所有其他的步骤,即记录预期、重放和验证都没有改变。只有嘲讽受到这一变化的影响。
@ExtendWith(EasyMockExtension.class)
public class EasyMockTestsWithAnnotationsJUnit5 {
@Mock
RecordDao mockDao;
@Mock
SequenceGenerator mockGenerator;
@TestSubject
RecordService service = new RecordService(mockGenerator, mockDao);
@Test
public void whenSaveCorrectRecord_ItSavedSuccessfully() {
//test code
}
}
4.4.一个使用EasyMockSupport的测试
除了创建EasyMockSupport 的实例,我们还可以从EasyMockSupport中扩展测试类。通过这种方式,我们可以直接访问*replayAll()和verifyAll()*方法。
@ExtendWith(EasyMockExtension.class)
public class EasyMockTestsWithEasyMockSupport extends EasyMockSupport {
@Test
public void whenSaveCorrectRecord_ItSavedSuccessfully() {
//create mock
//record expecations
replayAll();
//test operation
verifyAll();
}
}
5.高级概念
5.1.Mock vs Strict Mock vs Nice Mock
EasyMock 支持三种类型的mock对象。使用下面的方法来创建mock。
EasyMock.mock()EasyMock.strictMock()EasyMock.niceMock()
我们也可以使用EasyMock.createMock() 方法来创建这些mock。
//Default Mock
EasyMock.createMock(RecordDao.class);
//---or---
EasyMock.createMock(MockType.DEFAULT, RecordDao.class);
//Nice Mock
EasyMock.createMock(MockType.NICE, RecordDao.class);
//Strict Mock
EasyMock.createMock(MockType.STRICT, RecordDao.class);
在验证记录的期望时,这些mock的行为是不同的。
- 默认的模拟。如果一个方法被调用而不是预期的,或者一个预期的方法没有被调用,则测试失败。方法调用的顺序并不重要。
- 漂亮的模拟。如果一个方法被期望但没有被调用,测试就会失败。被调用但未被期望的方法将被返回一个与类型相适应的默认值*(0*、null或false)。方法调用的顺序并不重要。
- 严格模拟(Strict Mock)。类似于默认的mock,除了方法调用的顺序是重要的。
注意,对于由mock() 和strictMock() 创建的mock,任何意外的方法调用都会导致AssertionError 。
niceMock() ,当方法返回一个类型合适的缺省值时,允许在模拟上进行任何意外的方法调用而不会导致测试失败。
5.2.嘲弄异常
为了能够测试一个方法在需要时抛出适当的异常,一个模拟对象必须能够在调用时抛出一个异常。
使用**andThrow()**方法来记录一个异常类的期望值。
EasyMock.expect(...)
.andThrow(new IOException());
6.结语
在这个EasyMock教程中,我们学会了用Junit配置easymock,并在junit 4和junit 5平台下执行测试。我们学习了用easymock测试的基本概念,包括mock、expect、replay和verify等测试步骤。
最后,我们学会了用一个例子来写一个完整的测试。
学习愉快!!
这篇文章有帮助吗?
如果你喜欢这篇文章,请告诉我们。这是我们能够改进的唯一方法。
是的
没有