对于那些还没有花时间去全面研究手头的问题的人来说,这可能是一个永恒的问题,但这里有...
测试是这样的:
@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);
}
在标记为HERE
的点上,测试失败......不时(双关语)。但我不明白为什么。所以,我将在这里展开代码。
首先:source
是mock(MessageSource.class)
(明确定义测试类),MessageSource
代码如下:
public interface MessageSource
{
String getKey(final String key);
}
第二:loader
是mock(MessageSourceLoader.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 ms到期的加载器,并尝试第一次读取它,然后暂停50 ms,再次读取,然后暂停50 ms,然后第三次读取;我想确保过期有效,因为我打算使用mockito的链式.thenReturn()
调用
答案 0 :(得分:0)
我会以不同的方式解决这个问题。直接在代码中使用时间是错误的。你需要的是TimeService。那么你的问题将很容易解决,你可以模拟TimeService。