一、Mock
1.1 什么是 Mock
mock 是在测试过程中,对于一些不容易构造/获取的对象,创建一个 mock 对象来模拟对象的行为。
1.2 什么时候使用
* 单元测试时,使用外部资源或第三方库代码
* 并行开发时,另一方还没有开发完毕
1.3 Mock 分类
- Mock 对象
主要适用于单元测试,写入一些预期的值,通过它进行自己想要的测试。
- Mock Server
主要适用于接口和性能测试,构造一个假的服务返回预期的结果,进行测试。
1.4 技术选型
在 Java 阵营 中主要的 Mock 测试工具有 **Mockito ,JMock,MockCreator,Mockrunner,EasyMock,MockMaker,PowerMock ** 等
我们这里重点讲解 **Mockito 和 PowerMock ** 。
二、Mockito 实践
2.1 引入依赖
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
2.2 验证行为
@Test
public void testList(){
// 创建模拟对象
List<String> mockList = mock(List.class);
// 模拟对象执行操作
mockList.add("one");
mockList.clear();
// 验证
verify(mockList).add("one");
verify(mockList).clear();
}
执行程序,发现测试通过,证明 Mockito 可以验证行为。
2.3 验证行为调用次数
@Test
public void testListBehaviorExecutionFrequency() {
// 创建模拟对象
List<String> mockList = mock(List.class);
// 模拟对象执行操作
mockList.add("1");
mockList.add("2");
mockList.add("2");
mockList.add("3");
mockList.add("3");
mockList.add("3");
mockList.add("4");
mockList.add("4");
mockList.add("4");
mockList.add("4");
mockList.clear();
// 验证
verify(mockList).add("1");
// 验证执行次数
verify(mockList, times(1)).add("1");
verify(mockList, times(2)).add("2");
verify(mockList, times(3)).add("3");
verify(mockList, times(4)).add("4");
verify(mockList, times(1)).clear();
// 验证从来没有执行的方法
verify(mockList, never()).add("0");
// 至少执行过一次
verify(mockList, atLeastOnce()).add("4");
// 最小执行过2次
verify(mockList, atLeast(2)).add("4");
// 最多执行过5次
verify(mockList, atMost(5)).add("4");
}
2.4 验证行为执行的顺序
@Test
public void testListBehaviorExecutionOrderBySingle(){
// 创建模拟对象
List<String> mockList = mock(List.class);
// 模拟对象执行操作
mockList.add("1");
mockList.add("2");
mockList.add("3");
InOrder inOrder = inOrder(mockList);
// 验证先执行add 1 ,再执行添加 3,可以发现跳过了添加 2 的行为,说明可以只关注感兴趣的交互即可
inOrder.verify(mockList).add("1");
inOrder.verify(mockList).add("3");
}
@Test
public void testListBehaviorExecutionOrderByMultiple() {
// 创建模拟对象
List<String> firsMockList = mock(List.class);
List<String> secondMockList = mock(List.class);
// 模拟对象执行操作
firsMockList.add("firsMockList add ");
secondMockList.add("secondMockList add ");
InOrder inOrder = inOrder(firsMockList,secondMockList);
// 验证执行顺序
inOrder.verify(firsMockList).add("firsMockList add ");
inOrder.verify(secondMockList).add("secondMockList add ");
}
2.5 验证从未调用过的对象
@Test
public void testNoOperating() {
List<String> notOperatingList = mock(List.class);
verifyNoInteractions(notOperatingList);
}
2.6 查找冗余调用
@Test
public void testNoMoreInteractions(){
List<String> mockList = mock(List.class);
mockList.add("one");
mockList.add("two");
verify(mockList).add("one");
//将抛出异常,因为 `mockList.add("two");` 是额外的调用
verifyNoMoreInteractions(mockList);
}
2.7 模拟存根
@Test
public void testStubbing(){
// 创建模拟对象
List mockList = mock(List.class);
// 启用存根方法,当什么时候执行什么操作
when(mockList.get(0)).thenReturn("1");
when(mockList.get(3)).thenReturn("3");
Assert.assertEquals("1",mockList.get(0));
Assert.assertEquals("3",mockList.get(3));
Assert.assertEquals(null,mockList.get(99));
}
说明:
* 默认情况下,对于所有返回值的方法,模拟将酌情返回null,原始/原始包装器值或空集合。例如,对于int / Integer为0,对于boolean / Boolean为false。
* 存根可以被覆盖:例如,通用存根可以进入夹具设置,但是测试方法可以覆盖它。请注意,过多的存根是潜在的代码异味,表明存在过多的存根
* 一旦存根,该方法将始终返回存根值,而不管其被调用了多少次。
* 最后一次存根更为重要-当您多次对具有相同参数的相同方法进行存根时。换句话说:存根的顺序很重要,但很少有意义,例如,存根完全相同的方法调用时或有时使用参数匹配器时,等等。
2.8 模拟异常抛出
@Test
public void testThrowException(){
// 创建模拟对象
List mockList = mock(List.class);
// 指定什么操作将抛出异常
doThrow(new RuntimeException()).when(mockList).clear();
// 该方法将抛出异常
mockList.clear();
}
2.9 模拟创建的简写- @Mock
注释
@RunWith(MockitoJUnitRunner.class)
public class MockTest{
@Mock
private List list;
@Test
public void testMockAnnotation(){
list.add("one");
verify(list).add("one");
}
}
需要配合 @RunWith(MockitoJUnitRunner.class)
注解使用。
2.10 参数匹配器
实现类:
@Service
public class UserServiceImpl implements UserService {
private final SysUserMapper sysUserMapper;
public UserServiceImpl(SysUserMapper sysUserMapper) {
this.sysUserMapper = sysUserMapper;
}
@Override
public int count(String account) {
SysUserExample example = new SysUserExample();
example.createCriteria().andAccountEqualTo(account);
List<SysUser> users = sysUserMapper.selectByExample(example);
return users.size();
}
}
测试类:
public class UserServiceTest {
private UserService userService;
private SysUserMapper sysUserMapper;
@Before
public void setUp() {
sysUserMapper = mock(SysUserMapper.class);
userService = new UserServiceImpl(sysUserMapper);
}
@Test
public void testMock() {
// 准备数据
String account1 = "admin";
List<SysUser> userList = new ArrayList<>();
userList.add(getOneUser(account1));
// 模拟行为
when(sysUserMapper.selectByExample(any(SysUserExample.class)))
.thenReturn(userList);
// 执行方法
int result = userService.count(account1);
// 验证结果
assertThat(result, is(1));
}
}
其他类型参数:
anyInt();
anyString();
anyX();
更多使用方法请查看 文档
三、PowerMock 实践
PowerMock 主要用于静态和私有方法的模拟。
3.1 引入依赖
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
3.2 使用说明
相关注解:所有测试类均须加上以下注解
// 表明用 PowerMockerRunner来运行测试用例,否则无法使用PowerMock
@RunWith(PowerMockRunner.class)
// 所有需要测试的类,列在此处,以逗号分隔
@PrepareForTest({UserController.class, FileHelper.class})
3.3 模拟静态方法调用
public class PrintUtils {
public static String getPirntMessage() {
return "123";
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(PrintUtils.class)
public class PrintUtilsTest {
@Test
public void shouldReturnExpectedString() {
String expectedString = "mockString";
PowerMockito.mockStatic(PrintUtils.class);
PowerMockito.when(PrintUtils.getPirntMessage()).thenReturn(expectedString);
String result = PrintUtils.getPirntMessage();
assertThat(result,is(expectedString));
}
}
更多使用方法请查看 文档