2010-08-09 27 views
1

函數(無副作用)是這樣一個基本的構建塊,但我不知道在Java中測試它們的令人滿意的方式。我該如何簡化Java中副作用免費方法的測試?

我正在尋找使測試更簡單的技巧。下面是我想要的一個例子:我在尋找一些註釋我可以把測試(S)

public void setUp() { 
    myObj = new MyObject(...); 
} 

// This is sooo 2009 and not what I want to write: 
public void testThatSomeInputGivesExpectedOutput() { 
    assertEquals(expectedOutput, myObj.myFunction(someInput); 
    assertEquals(expectedOtherOutput, myObj.myFunction(someOtherInput); 
    // I don't want to repeat/write the following checks to see 
    // that myFunction is behaving functionally. 
    assertEquals(expectedOutput, myObj.myFunction(someInput); 
    assertEquals(expectedOtherOutput, myObj.myFunction(someOtherInput); 

} 


// The following two tests are more in spirit of what I'd like 
// to write, but they don't test that myFunction is functional: 
public void testThatSomeInputGivesExpectedOutput() { 
    assertEquals(expectedOutput, myObj.myFunction(someInput); 
} 

public void testThatSomeOtherInputGivesExpectedOutput() { 
    assertEquals(expectedOtherOutput, myObj.myFunction(someOtherInput); 
} 

,爲MyObject或myFunction的讓測試框架自動重複調用給myFunction在所有可能的排列爲給出的給定輸入/輸出組合,或可能排列的一些子集,以證明函數是有效的。

例如,上面的(僅)兩種可能的排列是:

  • MyObj中=新的MyObject();
  • myObj.myFunction(someInput);
  • myObj.myFunction(someOtherInput);

和:

  • MyObj中=新的MyObject();
  • myObj.myFunction(someOtherInput);
  • myObj.myFunction(someInput);

我應該只能提供輸入/輸出對(someInput,expectedOutput)和(someOtherInput,someOtherOutput),框架應該完成剩下的工作。

我還沒有使用QuickCheck,但它似乎是一個非解決方案。它被記錄爲一個生成器。我不是在尋找一種方法來爲我的函數生成輸入,而是一個框架,它允許我聲明性地指定我的對象的哪一部分是無副作用的,並使用基於該聲明的一些置換來調用我的輸入/輸出規範。

更新:我不想驗證對象中沒有任何更改,memoizing函數是這種測試的典型用例,並且memoizer實際上會更改其內部狀態。但是,給定一些輸入的輸出總是保持不變。

+0

就這樣說,「隨機」和「測試」不會很好地結合在一起 - 您應該能夠重複完全相同的測試並獲得完全相同的結果,如果您使用隨機數而不是定義的輸入。你不希望一次測試失敗,因爲它使用了一個「壞」輸入,然後再傳一次,因爲該輸入不是測試的一部分。 – cHao 2010-08-09 07:14:41

+0

刪除了隨機詞。我不是在尋找隨機的,我正在尋找所有排列的合理子集。 – user239558 2010-08-09 07:42:18

+0

@cHao - 但是用一個固定的種子僞隨機並不是一個完全不好的想法......並且可以給你一個很好的排列子集。但坦率地說,我認爲這種測試對於檢查副作用是不合適的。 – 2010-08-09 07:43:31

回答

0

這聽起來像你試圖測試調用某個類的特定方法不會修改它的任何字段。這是一個有點奇怪的測試案例,但完全有可能爲它寫一個明確的測試。對於其他「副作用」,如調用其他外部方法,這有點困難。您可以用測試存根替換本地引用,並驗證它們沒有被調用,但是您仍然不會通過這種方式捕獲靜態方法調用。儘管如此,通過檢查來驗證你在代碼中沒有這樣做,並且有時候必須足夠好,這是微不足道的。

這裏有一種方法來測試,有沒有副作用在呼叫:

public void test_MyFunction_hasNoSideEffects() { 
    MyClass systemUnderTest = makeMyClass(); 
    MyClass copyOfOriginalState = systemUnderTest.clone(); 
    systemUnderTest.myFunction(); 
    assertEquals(systemUnderTest, copyOfOriginalState); //Test equals() method elsewhere 
} 

這有點不尋常,試圖證明一個方法是真正的無副作用。單元測試通常試圖證明一個方法的行爲是正確的並且是根據契約的,但它們並不意味着替代檢查代碼。檢查一個方法是否有任何可能的副作用通常是一個非常簡單的練習。如果你的方法永遠不會設置一個字段的值,也不會調用任何非函數方法,那麼它就是功能性的。

在運行時測試它很棘手。更有用的可能是某種靜態分析。也許你可以創建一個@Functional註解,然後編寫一個程序來檢查你的程序的類是否有這些方法,並檢查它們是否只調用其他的@Functional方法,而不會指派給字段。

隨機搜索,我發現有人正是這個話題的master's thesis。也許他有可用的工作代碼。

不過,我會重複一遍,我建議您將注意力集中在其他地方。雖然你大多可以證明一種方法根本沒有副作用,但在許多情況下,通過視覺檢查來快速驗證這一點可能會更好,並將剩下的時間用於其他更基本的測試。

+0

有趣的觀察。這不是我正在尋找的東西。我更新了這個問題,以便更清楚。記憶功能在我想測試的功能範圍內。 – user239558 2010-08-09 07:54:18

+0

如果您的equals()方法不包含您的memoize的數據(除了可能驗證存在的memoize的數據在兩者之間不一致),它仍然可以工作。 – 2010-08-09 08:04:47

7

如果您試圖測試功能副作用釋放,然後用隨機參數調用並不真的會削減它。這同樣適用於具有已知參數的隨機調用序列。或者是隨機的,隨機或固定的種子。很有可能只有隨機數發生器選擇的任何一系列調用都會發生(有害)副作用。

在您正在進行的任何調用的輸出中,不管輸入是什麼,還有一種可能是副作用實際上不會顯示。它們的副作用可能在您認爲不需要檢查的其他相關對象上。

如果你想測試這種類型的東西,你真的需要實現一個「白盒」測試,在你看代碼的時候,試着弄清楚什麼可能會導致(不需要的)副作用並創建測試用例基於這些知識。但我認爲更好的方法是仔細的手動代碼檢查,或使用自動靜態代碼分析器......如果你能找到一個能爲你完成這項工作的人。

OTOH,如果你已經知道該功能是免費的副作用,實施隨機測試「萬一」是有點浪費時間,國際海事組織。

+1

我完全同意。你只有很多時間和精力。手動驗證你的方法是無副作用的,在它上面加上一個評論,說'//功能 - 不要修改字段!!!! 1!',然後繼續。 – 2010-08-09 07:25:00

+1

我不想用隨機參數調用該方法。 我*正*尋找的東西,會讓我有信心,影響輸出的副作用不會發生在一個特定的調用序列。 – user239558 2010-08-09 07:45:37

+0

@ user239558 - 然後編碼特定的呼叫序列。但實際上,在另一個評論中,你正在談論呼叫序列可能排列的一個子集。除非你手動選擇子集,否則在測試中有一個隨機性元素。 – 2010-08-09 09:19:39

3

我不是很確定我明白你在問什麼,但是看起來像Junit Theories(http://junit.sourceforge.net/doc/ReleaseNotes4.4.html#theories)可能是一個答案。

+0

我認爲Junit Theories可以完成我正在尋找的任務,即從理論上分離數據點。但是,我找不到有關理論如何測試的文檔。我想要一個像「FunctionalTheory」runner的東西,它將稍微排列數據點並重新測試以檢查對象是否在功能上行爲。 – user239558 2010-08-09 08:59:25

1

在本例中,您可以創建一個鍵/值對的映射(輸入/輸出),並使用從地圖中拾取的值多次調用待測方法。這不會證明,該方法是功能性的,但會增加概率 - 這可能是足夠的。

這裏有這樣一個額外的大概功能性測試的一個簡單的例子:

@Test public probablyFunctionalTestForMethodX() { 
    Map<Object, Object> inputOutputMap = initMap(); // this loads the input/output values 
    for (int i = 0; i < maxIterations; i++) { 
    Map.Entry test = pickAtRandom(inputOutputMap); // this picks a map enty randomly 
    assertEquals(test.getValue(), myObj.myFunction(test.getKey()); 
    } 
} 

具有較高的複雜性問題可以得到解決基於Command模式:你可以包裝在命令對象的測試方法,將命令對象添加到列表中,根據該列表對列表進行洗牌並執行命令(=嵌入式測試)。

+0

是的,這是我想要的,但我想知道是否有更多的聲明方式。這似乎是函數式編程的一個非常基本的屬性,它似乎應該由測試框架來支持。 – user239558 2010-08-09 07:56:02

0

我害怕我不再找到鏈接,但Junit 4有一些幫助函數來生成測試數據。它是這樣的:

pubic void testData() { 
    data = {2, 3, 4}; 
    data = {3,4,5 }; 
... 
return data; 
} 

Junit將然後你的方法將這個數據。但正如我所說的,我無法找到鏈接(忘記關鍵字)瞭解詳細的(和正確的)示例。

+0

你指的是http://junit.sourceforge.net/javadoc/org/junit/runners/Parameterized.html,但它會運行一個新的實例每個測試 – Arjan 2010-08-09 09:14:34

+0

@arjantop是的,這就是我的意思。太糟糕了,這裏沒有用處。 – InsertNickHere 2010-08-09 10:14:35

0

JUnit中,你可以編寫自己的測試跑步者的大量相同的測試一個簡單的方法。此代碼是沒有測試(我不知道,如果它獲得參數的方法將被識別爲測試方法,也許需要一些更多的亞軍設置?):

public class MyRunner extends BlockJUnit4ClassRunner { 

    @Override 
    protected Statement methodInvoker(final FrameworkMethod method, final Object test) { 
     return new Statement() { 
      @Override 
      public void evaluate() throws Throwable { 
       Iterable<Object[]> permutations = getPermutations(); 
       for (Object[] permutation : permutations) { 
        method.invokeExplosively(test, permutation[0], permutation[1]); 
       } 
      } 
     }; 
    } 

} 

應該只提供getPermutations的問題( )的實施。例如,它可以從某些List<Object[]>字段獲取數據,並使用某些自定義註釋進行註釋並生成所有排列。

0

我認爲你錯過的這個詞是「參數化測試」。然而,在.Net風格中,jUnit似乎更乏味。在NUnit中,以下測試用所有組合執行6次。

[Test] 
public void MyTest(
    [Values(1,2,3)] int x, 
    [Values("A","B")] string s) 
{ 
    ... 
} 

對於Java,您的選擇似乎是:

  • 的JUnit supports this與第4版。但是這是一個很大的代碼(現在看來,JUnit是堅定的關於測試方法不採取參數)。這是侵入性最小的。
  • DDSteps,一個jUnit插件。請參閱this video,它採用適當命名的Excel電子表格中的值。您還需要編寫一個映射器/夾具類,將電子表格中的值映射到夾具類的成員,然後用於調用SUT。
  • 最後,你有Fit/Fitnesse。它與DDSteps一樣好,除了輸入數據是HTML/Wiki格式。您可以從Excel表格粘貼到Fitnesse中,只需按下按鈕即可正確格式化。你也需要在這裏寫一個夾具課程。