Java 依赖时间的测试,如何确保成功?
这可能是一个永恒的问题,来自一个还没有花时间来全面调查手头问题的人,但接下来 测试是这样的:Java 依赖时间的测试,如何确保成功?,java,time,testng,mockito,Java,Time,Testng,Mockito,这可能是一个永恒的问题,来自一个还没有花时间来全面调查手头问题的人,但接下来 测试是这样的: @Test public void expiryWorksAsExpected() throws IOException, InterruptedException { final MessageSource source2 = mock(MessageSource.class); final MessageSource source3 = mock(MessageSource.
@Test
public void expiryWorksAsExpected()
throws IOException, InterruptedException
{
final MessageSource source2 = mock(MessageSource.class);
final MessageSource source3 = mock(MessageSource.class);
when(loader.load(any(Locale.class)))
.thenReturn(source)
.thenReturn(source2)
.thenReturn(source3);
final MessageSourceProvider provider = builder.setLoader(loader)
.setExpiryTime(10L, TimeUnit.MILLISECONDS).build();
final MessageSource first = provider.getMessageSource(Locale.ROOT);
TimeUnit.MILLISECONDS.sleep(50L);
final MessageSource second = provider.getMessageSource(Locale.ROOT);
TimeUnit.MILLISECONDS.sleep(50L);
final MessageSource third = provider.getMessageSource(Locale.ROOT);
verify(loader, times(3)).load(Locale.ROOT); // HERE
assertSame(first, source);
assertSame(second, source2);
assertSame(third, source3);
}
在此处标记的点处,测试失败。。。不时的双关语。但我不明白为什么。因此,我将在这里展开代码
首先:source是一个mockMessageSource.class,定义测试类,MessageSource代码如下:
public interface MessageSource
{
String getKey(final String key);
}
第二:loader是一个mockMessageSourceLoader.class,它是:
public interface MessageSourceLoader
{
MessageSource load(final Locale locale)
throws IOException;
}
第三:builder是一个LoadingMessageSourceProvider.builder;下面是完整的代码,注释仍然很长,很抱歉:
@ThreadSafe
public final class LoadingMessageSourceProvider
implements MessageSourceProvider
{
private static final ThreadFactory THREAD_FACTORY = new ThreadFactory()
{
private final ThreadFactory factory = Executors.defaultThreadFactory();
@Override
public Thread newThread(final Runnable r)
{
final Thread ret = factory.newThread(r);
ret.setDaemon(true);
return ret;
}
};
// From a custom API -- more details on demand
private static final InternalBundle BUNDLE = InternalBundle.getInstance();
private static final int NTHREADS = 3;
private final ExecutorService service
= Executors.newFixedThreadPool(NTHREADS, THREAD_FACTORY);
private final MessageSourceLoader loader;
private final MessageSource defaultSource;
private final long timeoutDuration;
private final TimeUnit timeoutUnit;
private final AtomicBoolean expiryEnabled;
private final long expiryDuration;
private final TimeUnit expiryUnit;
private final Map<Locale, FutureTask<MessageSource>> sources
= new HashMap<Locale, FutureTask<MessageSource>>();
private LoadingMessageSourceProvider(final Builder builder)
{
loader = builder.loader;
defaultSource = builder.defaultSource;
timeoutDuration = builder.timeoutDuration;
timeoutUnit = builder.timeoutUnit;
expiryDuration = builder.expiryDuration;
expiryUnit = builder.expiryUnit;
expiryEnabled = new AtomicBoolean(expiryDuration == 0L);
}
public static Builder newBuilder()
{
return new Builder();
}
@Override
public MessageSource getMessageSource(final Locale locale)
{
if (!expiryEnabled.getAndSet(true))
setupExpiry(expiryDuration, expiryUnit);
FutureTask<MessageSource> task;
synchronized (sources) {
task = sources.get(locale);
if (task == null) {
task = loadingTask(locale);
sources.put(locale, task);
service.execute(task);
}
}
try {
final MessageSource source = task.get(timeoutDuration, timeoutUnit);
return source == null ? defaultSource : source;
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
return defaultSource;
} catch (ExecutionException ignored) {
return defaultSource;
} catch (TimeoutException ignored) {
return defaultSource;
} catch (CancellationException ignored) {
return defaultSource;
}
}
private FutureTask<MessageSource> loadingTask(final Locale locale)
{
return new FutureTask<MessageSource>(new Callable<MessageSource>()
{
@Override
public MessageSource call()
throws IOException
{
return loader.load(locale);
}
});
}
private void setupExpiry(final long duration, final TimeUnit unit)
{
final Runnable runnable = new Runnable()
{
@Override
public void run()
{
final List<FutureTask<MessageSource>> tasks;
synchronized (sources) {
tasks = new ArrayList<FutureTask<MessageSource>>(
sources.values());
sources.clear();
}
for (final FutureTask<MessageSource> task: tasks)
task.cancel(true);
}
};
// Overkill?
final ScheduledExecutorService scheduled
= Executors.newScheduledThreadPool(1, THREAD_FACTORY);
scheduled.scheduleAtFixedRate(runnable, duration, duration, unit);
}
public static final class Builder
{
private MessageSourceLoader loader;
private MessageSource defaultSource;
private long timeoutDuration = 1L;
private TimeUnit timeoutUnit = TimeUnit.SECONDS;
private long expiryDuration = 10L;
private TimeUnit expiryUnit = TimeUnit.MINUTES;
private Builder()
{
}
public Builder setLoader(final MessageSourceLoader loader)
{
BUNDLE.checkNotNull(loader, "cfg.nullLoader");
this.loader = loader;
return this;
}
public Builder setDefaultSource(final MessageSource defaultSource)
{
BUNDLE.checkNotNull(defaultSource, "cfg.nullDefaultSource");
this.defaultSource = defaultSource;
return this;
}
public Builder setLoadTimeout(final long duration, final TimeUnit unit)
{
BUNDLE.checkArgument(duration > 0L, "cfg.nonPositiveDuration");
BUNDLE.checkNotNull(unit, "cfg.nullTimeUnit");
timeoutDuration = duration;
timeoutUnit = unit;
return this;
}
public Builder setExpiryTime(final long duration, final TimeUnit unit)
{
BUNDLE.checkArgument(duration > 0L, "cfg.nonPositiveDuration");
BUNDLE.checkNotNull(unit, "cfg.nullTimeUnit");
expiryDuration = duration;
expiryUnit = unit;
return this;
}
public Builder neverExpires()
{
expiryDuration = 0L;
return this;
}
public MessageSourceProvider build()
{
BUNDLE.checkArgument(loader != null, "cfg.noLoader");
return new LoadingMessageSourceProvider(this);
}
}
}
现在的问题是:我不时看到考试失败;更具体地说,在检查加载程序是否已被精确调用三次的行中。尽管我以前从未见过测试失败,毫秒延迟可能太短等等,但我想确保这样的测试运行并成功——我想测试我的逻辑。我如何做到这一点,而不必求助于10毫秒的到期时间和不合理的(比如)两次取回之间的2秒睡眠
编辑测试的目的是验证是否遵守了到期时间;在这里,我设置了一个10毫秒到期的加载程序,第一次尝试读取它,然后暂停50毫秒,第二次读取,然后暂停50毫秒,然后第三次读取;我想确保使用mockito的chained时,有效期能够正常工作。然后返回调用我将以不同的方式处理这个问题。在代码中直接使用时间是错误的。你需要的是时间服务。那么你的问题就很容易解决了,你可以模拟TimeService。如果我正确理解你的问题,我想我会分离出消息获取的概念。因此,一些类似于注入过期策略的东西。在单元测试期间,您可以通过提供一个可以完全控制的伪版本来离散地控制这一点。在您的生产代码中,您可以使用TimedExpirationPolicy,它将在您可以完全独立于此类进行测试的计时器上工作
你不仅要让你的测试变得不那么离谱,还要更好地坚持单一责任原则。你能用一句话或一小段话解释一下你测试的预期逻辑吗?还没有读过全部内容,但是你能使用依赖注入来控制你的应用程序对时间的感知而不受实际时间流逝的影响吗?@chrylis最后看到了post edit-我承认post太长了,但我现在不能,请看一个更简洁和解释性的方式来表达手头的需求:/如果这一点完全可以做到,我会感到惊讶。在我看来,所有给您带来问题的时间细节实际上都是在测试scheduleAtFixedRate方法,而不是您编写的任何代码。您确定要测试JDK本身吗?