背景
在业务代码中,会有需要线程sleep的情况,通常会使用Thread.sleep
和TimeUnit.xxx.sleep
时当前线程暂停执行。
但是在编写单元测试时,如果不对sleep
方法进行打桩,会导致单元测试执行时间过长。
举例
当前有一个Add类,在返回结果前sleep 1分钟,如果不对sleep进行打桩,单元测试用例也会执行1分钟。 功能实现类:
public class Add {
public int add(int a, int b) throws InterruptedException {
Thread.sleep(60 * 1000);
return a + b;
}
}
单元测试类:
class AddTest {
@Test
void testAdd() throws InterruptedException {
Add add = new Add();
int ret = add.add(1, 2);
assertEquals(3, ret);
}
}
执行结果:
可以看到testAdd这个用例执行了1分钟才结束。
解决方法
使用Thread.sleep的情况
对于使用Thread.sleep
的情况,可以通过打桩Thread.class的方式解决。
// 业务逻辑
public class Add {
public int add(int a, int b) throws InterruptedException {
Thread.sleep(60 * 1000);
return a + b;
}
}
// 单元测试
@RunWith(PowerMockRunner.class)
@PrepareForTest({Add.class})
class AddTest {
@Test(expected = InterruptedException.class)
public void testAdd() throws InterruptedException {
PowerMockito.mockStatic(Thread.class);
PowerMockito.doThrow(new InterruptedException()).when(Thread.class);
Thread.sleep(anyLong());
Add add = new Add();
int ret = add.add(1, 2);
Assert.assertEquals(3, ret);
}
}
使用TimeUnit.MINUTE.sleep的情况
如果使用TimeUnit.MINUTE.sleep来实现线程sleep,直接打桩Thread.class是无效的。
需要使用下面的方式进行打桩:
// 业务代码
public class Add {
public int add(int a, int b) throws InterruptedException {
TimeUnit.MINUTES.sleep(1);
return a + b;
}
}
// 单元测试
@RunWith(PowerMockRunner.class)
@PrepareForTest({Add.class})
class AddTest {
@Test(expected = InterruptedException.class)
public void testAdd() throws InterruptedException {
TimeUnit mockTimeUnit = PowerMockito.mock(TimeUnit.MINUTES.getDeclaringClass());
Whitebox.setInternalState(TimeUnit.class, "MINUTES", mockTimeUnit);
PowerMockito.doThrow(new InterruptedException()).when(mockTimeUnit).sleep(anyLong());
Add add = new Add();
int ret = add.add(1, 2);
Assert.assertEquals(3, ret);
}
}
其他
1. PrepareForTest中是调用sleep方法的类
在本例中,Add这个类调用了Thread和TimeUnit的sleep方法,所以需要将Add.class放入PrepareForTest中。
不需要将Thread.class或者TimeUnit.class放入PrepareForTest中。
2. 除了doThrow外,也可以使用doNothing
除了使用InterruptedException异常来打断线程的sleep外,也可以使线程的sleep正常结束。只需要将上面代码中的doThrow替换为doNothing即可。
两种方法的区别是:
- 如果调用sleep函数的方法使用了try-catch块并且能够捕获InterruptedException,抛出异常会时方法走进catch代码块。
- 如果doNothing则不会进入catch代码块。
3. 实际测试发现,上面代码的行为与Java、Mockito的版本有关
上面的代码使用的依赖库的版本如下:
artifactId | 版本 |
---|---|
Jdk | 1.8 |
mockito-core | 2.28.2 |
powermock-module-junit4 | 2.0.9 |
powermock-api-mockito2 | 2.0.9 |
junit | 4.13.2 |