2010-09-30 49 views
3

由於我的應用程序域的數據結構到最近變得非常複雜,我開始閱讀模擬對象。很快出現了一個簡單的問題,但迄今爲止,答案已經證明是相當頭痛的。所以這裏有雲:如何在單元測試中使用模擬手法避免誤報?

我們有一類「富」與「酒吧」作爲其中的一個方法:

class Foo { 
    public String bar(int i){ 
     if(i == 1) return "arrr!"; 
    } 
} 

而且我們有一個類海盜調用Foo.bar(1);在其中的一個方法:

class Pirate { 
    public String yell(){ 
     Foo foo = new Foo(); 
     return foo.bar(1); 
    } 

現在我們嘲笑Foo類的海盜類的單元測試,因爲富恰好有其他依賴過多:

@Test 
public void returnsPirateString() { 
    Pirate blackBeard = new Pirate(); 
    Foo fooMock = mock(Foo.class); 
    fooMock.expectAndReturn("bar",1,"arrr!"); //expects 'bar' function to be called once and returns "arrr!" 
    assertEquals(blackBeard.yell(),"arrr!"); 
} 

現在會發生什麼,是如果我們重構方法欄來返回null而不是「arrr!」,我們的測試將繼續愉快地運行,而我們的程序不能按照我們想要的方式工作。這可能會導致可能的調試噩夢。

使用mockist方法而不是單元測試的經典測試方法,大多數時候所有「helper」對象都會被嘲笑,只有被測試的對象纔會被解除鎖定,所以前面提到的問題也會經常發生。

可以做什麼來防止這個問題,而嘲笑?

回答

2

你應該單獨測試'helper object'。一旦對這兩者進行了覆蓋和測試,那麼您可以確定兩者都以預期的方式相互影響。

更改您的'幫助對象'是應該與該幫助對象進行測試以確認其仍然按預期行爲一樣。

如果你關心幫助和初級班的組合的具體運行時的行爲,那麼你應該在更高層次上使用集成測試,或其他一些測試,如預期般一起斷言兩項工作。

+0

是的,但是這有點不利於嘲笑的使用,因爲無論如何你必須創建你的數據結構來進行不同的測試。在這種情況下,我可以創建一個Object母體,並在這兩個測試中使用這個母親而不是嘲笑。 – tmetten 2010-09-30 14:45:02

+1

我不確定我是否遵循...嘲諷的要點是單獨測試您的不同類,以便您可以專注於測試受測試的特定類。在測試幫助者時,你應該測試那個幫助者與其他真正的類完全隔離。 – Pete 2010-09-30 15:06:18

+0

是的,通過這樣做,你的測試不會指出任何問題,而實際上課程會失敗。 – tmetten 2010-09-30 15:17:10

3

在測試中,您正在測試使用Foo的Pirate類的yell()方法。所以你必須嘲笑Foo的酒吧方法的行爲。 爲了確保您的bar方法正常工作,您需要另一個測試用例來測試Foo的bar方法。

@Test 
public void testBar() { 
    //make sure bar retrun "arrr"! 
} 

現在如果你的bar方法返回null,這個測試用例會失敗!

+0

是的,但是如果bar現在應該返回null呢? Foo的測試會失敗,但所有其他單元測試與嘲笑Foo類不會抱怨,而這些測試可能會失敗,而使用真實的對象,而不是嘲笑的。 – tmetten 2010-09-30 14:41:49

+1

當前酒吧的行爲以某種方式測試時,你當前的測試用例測試了yell()。爲了防止bar的不同行爲,您需要更多的yell()測試用例,每個測試用例都會測試一下bar的每個行爲。所以你會有像testYellWhenBarReturnsNormally(),testYellWhenBarThrowsException(),testYellWhenBarReturnsNull()這樣的測試用例。我知道這聽起來像很多工作,但您需要運用一些判斷來確定您需要哪些測試用例! – Sasi 2010-09-30 14:56:43

+0

但是,然後你基本上是黑盒測試,而不是白盒測試... – tmetten 2010-09-30 14:57:39

0

測試returnsPirateString不是假陽性 - !這是在測試時PirateFoo實例返回‘arrr會發生什麼’

換句話說,當你測試Pirate.yell不要緊什麼Foo.bar回報,除非它創建了一個特殊的邊界條件(和你應該已經有了一個測試,文檔什麼yell做時Foo返回null)。

Pirate.yell不負責保證對Foo.bar任何特定的返回值,所以它的單元測試不應該指望任何特定的回報values.You甚至應該改變你的測試使用其他的東西比的Foo.bar電流返回值的點。

+0

所以你說的是當我使用模擬對象我應該測試每個可能的特殊值/邊界情況?另外請注意,Bar最初的設計是爲了返回'arrr!'並突然被重構爲返回null。你必須跟蹤所有的單元測試,根據原始定義,這個函數被嘲弄,並將它們改爲重構的定義... – tmetten 2010-09-30 14:55:48

+0

不,你只需編寫足夠的單元測試來覆蓋有趣的情況。 'Pirate'不是保證'Foo.bar'返回值的業務(除非你通過向實際代碼添加斷言來完成它的部分職責)。我不會爲任何**單元測試編寫像示例中那樣簡單的外觀方法。 – 2010-09-30 14:59:50

+0

當然不是,我只是想提供一個簡單的點代碼示例。 :D事實上,這並不是保證Foo.bar返回值的海盜業務,但實際上,由於Foo.bar的返回值,Pirate.yell()將失敗。 – tmetten 2010-09-30 15:06:49