主題
我有一些代碼,是決然不是線程安全的:測試線程安全的失敗,斯波克
public class ExampleLoader
{
private List<String> strings;
protected List<String> loadStrings()
{
return Arrays.asList("Hello", "World", "Sup");
}
public List<String> getStrings()
{
if (strings == null)
{
strings = loadStrings();
}
return strings;
}
}
多個線程訪問getStrings()
同時有望看到strings
爲null
,從而loadStrings()
(這是一項昂貴的操作)被多次觸發。
我想使代碼線程安全的,而作爲世界的一個好公民,我寫了一個失敗的斯波克規範第一問題:
def "getStrings is thread safe"() {
given:
def loader = Spy(ExampleLoader)
def threads = (0..<10).collect { new Thread({ loader.getStrings() })}
when:
threads.each { it.start() }
threads.each { it.join() }
then:
1 * loader.loadStrings()
}
上面的代碼創建並啓動了10個線程每個呼叫getStrings()
。然後它聲稱loadStrings()
在所有線程完成時只被調用一次。
我預計這會失敗。然而,它始終通過。什麼?
經過涉及System.out.println
和其他無聊事情的調試會話後,我發現線程確實是異步的:它們的run()
方法以看似隨機的順序打印。然而,第一個線程訪問getStrings()
會總是是只有線程調用loadStrings()
。
怪異的一部分相當長的一段時間花在調試完畢後
沮喪,我使用JUnit 4的Mockito再次寫下了相同的測試:
@Test
public void getStringsIsThreadSafe() throws Exception
{
// given
ExampleLoader loader = Mockito.spy(ExampleLoader.class);
List<Thread> threads = IntStream.range(0, 10)
.mapToObj(index -> new Thread(loader::getStrings))
.collect(Collectors.toList());
// when
threads.forEach(Thread::start);
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// then
Mockito.verify(loader, Mockito.times(1))
.loadStrings();
}
這個測試持續失敗,因爲多次調用loadStrings()
,如預計。
問題
爲什麼斯波克測試一致通過,我將如何去與斯波克測試呢?
爲什麼有人想用代碼的原始語言來編寫測試代碼?正如你自己發現這使得調試噩夢。除了語言可能繼續前進以及如此受人喜愛的框架留下的事實之外。 –
@OlegSklyar我發現Spock/Groovy是一個用簡潔快捷的方式測試大多數Java代碼的好工具。我認爲這裏的問題與Spock框架本身的實現有關,因爲Groovy編譯爲Java字節碼,並且使用Java調試器相當容易。另外,這是一個我在工作中遇到的問題的簡單例子 - 我既不能拋棄Spock/Groovy也不能從Java遷移主代碼庫。 –
不幸的是我沒有你原來的問題的答案,所以對我來說這是一個哲學討論......但是任何進一步的框架的介紹增加了複雜性。現在這種複雜性已經讓你感到困擾。我相信當你決定重構代碼時,美麗的框架也會重構所有的測試。當然它不會。雖然是自制的,但我不得不在Java項目中處理類似的框架:在編寫Java測試庫之前修復或修改測試是一場噩夢。現在所有的測試都是可以理解和重構的。 –