使用
Thread.sleep()
加速测试的推荐方法是什么。
我正在测试一个具有重试功能的网络库,当连接断开或发生超时错误等时。但是,该库在重试之间使用
Thread.sleep()
(因此在服务器重新启动时它不会连接数千次)。该调用显着减慢了单元测试速度,我想知道有哪些选项可以覆盖它。
注意,我愿意实际更改代码,或使用模拟框架来模拟 Thread.sleep(),但想先听听您的意见/建议。
将与时间相关的功能委托给单独的组件通常是一个好主意。这包括获取当前时间,以及像 Thread.sleep() 这样的延迟。这样,在测试过程中就可以轻松地用模拟替换该组件,以及切换到不同的实现。
我刚刚遇到了类似的问题,我创建了一个
Sleeper
接口来抽象化这个问题:
public interface Sleeper
{
void sleep( long millis ) throws InterruptedException;
}
默认实现使用
Thread.sleep()
:
public class ThreadSleeper implements Sleeper
{
@Override
public void sleep( long millis ) throws InterruptedException
{
Thread.sleep( millis );
}
}
在我的单元测试中,我注入了
FixedDateTimeAdvanceSleeper
:
public class FixedDateTimeAdvanceSleeper implements Sleeper
{
@Override
public void sleep( long millis ) throws InterruptedException
{
DateTimeUtils.setCurrentMillisFixed( DateTime.now().getMillis() + millis );
}
}
这允许我查询单元测试中的时间:
assertThat( new DateTime( DateTimeUtils.currentTimeMillis() ) ).isEqualTo( new DateTime( "2014-03-27T00:00:30" ) );
请注意,您需要在测试开始时首先使用
DateTimeUtils.setCurrentMillisFixed( new DateTime( "2014-03-26T09:37:13" ).getMillis() );
修复时间,并在测试后使用 DateTimeUtils.setCurrentMillisSystem();
再次恢复时间
通过设置器配置睡眠时间,并提供默认值。因此,在单元测试中,使用一个小参数(例如 1)调用 setter,然后执行将调用
Thread.sleep()
的方法。
另一种类似的方法是通过布尔值进行配置,这样,如果
Thread.sleep()
设置为 boolean
,则根本不会调用 false
。
创建一些重试延迟类型来表示重试延迟的策略。为延迟调用策略类型的某种方法。随意嘲笑它。没有条件逻辑,或
true
/false
标志。只需注入您想要的类型即可。
在ConnectRetryPolicy.java中
public interface ConnectRetryPolicy {
void doRetryDelay();
}
在SleepConnectRetryPolicy.java中
public class final SleepConnectRetryPolicy implements ConnectRetryPolicy {
private final int delay;
public SleepConnectRetryPolicy(final int delay) {
this.delay = delay;
}
@Override
public void doRetryDelay() {
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
log.error("connection delay sleep interrupted", ie);
}
}
}
在MockConnectRetryPolicy.java中
public final class MockConnectRetryPolicy implements ConnectRetryPolicy {
@Override
public void doRetryDelay() {
// no delay
}
}
我会争论你为什么要测试 Thread.sleep。看来我是你试图测试某些事件的结果的行为。
即如果出现以下情况会发生什么:
如果您基于事件对代码进行建模,那么您可以测试发生特定事件时会发生什么,而不必提出一个屏蔽并发 API 调用的构造。否则你真正在测试什么?您是在测试应用程序对不同刺激的反应,还是只是测试 JVM 是否正常工作?
我同意其他读者的观点,有时对任何代码时间或线程相关的抽象进行抽象很有用,即虚拟时钟http://c2.com/cgi/wiki?VirtualClock,这样你就可以模拟任何计时/并发行为并专注于单位本身的行为。
听起来您应该采用一种状态模式,以便您的对象根据其所处的状态具有特定的行为。即 AwaitingConnectionState、ConnectionDroppedState。转换到不同的状态将通过不同的事件,即超时、断开连接等。不确定这是否满足您的需求,但它确实删除了很多条件逻辑,这些逻辑可能使代码更加复杂和不清楚。
如果您采用这种方式,那么您仍然可以在单元级别测试行为,同时仍然可以在稍后通过集成测试或验收测试进行现场测试。
尤金是对的,制作你自己的组件来包装不受你控制的系统我自己做了这个我想我会分享,这被称为“SelfShunt”检查一下:
Generator
是一个类,当您调用 getId()
时,它会返回当前系统时间。
public class GeneratorTests implements SystemTime {
private Generator cut;
private long currentSystemTime;
@Before
public void setup(){
cut = Generator.getInstance(this);
}
@Test
public void testGetId_returnedUniqueId(){
currentSystemTime = 123;
String id = cut.getId();
assertTrue(id.equals("123"));
}
@Override
public long currentTimeMillis() {
return currentSystemTime;
}
}
我们创建测试类“SelfShunt”并成为 SystemTime 组件,这样我们就可以完全控制时间。
public class BlundellSystemTime implements SystemTime {
@Override
public long currentTimeMillis(){
return System.currentTimeMillis();
}
}
我们包装不受我们控制的组件。
public interface SystemTime {
long currentTimeMillis();
}
然后创建一个接口,以便我们的测试可以“SelfShunt”
对于这些类型的情况,我实现了所有昏昏欲睡的类都实现的默认接口:
public interface ISleep {
/**
* Causes the currently executing thread to sleep
* @param milliseconds the length of time to sleep in milliseconds
* @throws InterruptedException if any thread has interrupted the current thread
*/
default void sleep(long milliseconds) throws InterruptedException {
Thread.sleep(milliseconds);
}
}
然后使用此方法代替
Thread.sleep()
来启用测试:
class MyClass implements ISleep {
void sleepyMethod() {
while(stuffNotFinished) {
doSomething();
sleep(1000);
}
}
public void doSomething() { /* ... */}
}
现在,编写快速测试非常简单,因为您可以简单地模拟对 sleep() 的调用以立即返回(使用 Mockito 的示例):
@Test void sleepyMethod_callsDoSomething() {
var objectUnderTest = Mockito.spy(new MyClass());
Mockito.doNothing().when(objectUnderTest).sleep(anyLong());
objectUnderTest.sleepyMethod();
Mockito.verify(objectUnderTest).doSomething();
}