以下代码是一个概述此单元测试目标的片段。下面演示createFile
仅执行一项已知为线程安全操作的任务。
public static final synchronized void createFile(final File file) throws IOException {
file.createNewFile();
}
@Test
public void testCreateFileThreadSafety() throws Exception {
for (int i = 1; i < 50; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
createFile(new File(i+".txt"));
new File(i+".txt").delete();
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}).start();
assertTrue(file.getParentFile().listFiles().length == 0);
}
}
修改
现在发生了什么:线程被创建,文件被创建,文件被删除,线程死亡,断言证明什么并重复
期待什么:线程应该全部启动并且断言应该确保一次只创建一个文件并且其他线程正在等待,而不是执行方法
DOUBLE EDIT:
我真正需要的是上述单元测试的重构,以便它完成它应该做的事情(如上所述)
答案 0 :(得分:1)
创建File
子类,它覆盖createNewFile
方法,如下所示:
class TestFile extends File {
private final AtomicBoolean isCreated = new AtomicBoolean(false);
private boolean isSynchronizationFailed = false;
public boolean createNewFile() throws IOException {
if (isCreated.compareAndSet(false, true)) {
// give other threads chance to get here
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
// cleanup
isCreated.set(false);
} else {
isSynchronizationFailed = true;
}
return super.createNewFile();
}
}
将此类的实例传递给您的线程
最后断言isSynchronizationFailed的测试为false。
如果两个线程以某种方式同时进入createNewFile方法,则将isSynchronizationFailed变量设置为true。
答案 1 :(得分:1)
当然对于这个非常简单的用例,它非常愚蠢,因为synchronized关键字就在那里。但一般来说,如果你想测试一个方法是否永远不会被同时调用,你可以抛出这个:
static AtomicInteger c = new AtomicInteger();
public void knownNonThreadSafeMethod(final File file) throws IOException {
int t = c.incrementAndGet();
doSomething();
Thread.yield(); //try to force race conditions, remove in prod
assert t == c.intValue();
}
如果您使用简单的int i.s.o. AtomicInteger,编译器优化将删除断言。
static int c = 0;
public void knownNonThreadSafeMethod(final File file) throws IOException {
int t = ++c;
doSomething();
assert t == c; //smart-ass compiler will optimize to 'true'
}
使用AtomicInteger,可以保证在所有CPU和所有线程上同步该值,因此您将检测到任何并发访问。
我知道它不是在JUnit测试中,但我找不到任何非侵入性的方法来解决这个问题。也许你可以通过AspectJ注入它?