unit-testing - 單元測試中的隨機數據?




tdd mocking (14)

我有一位同事為對象填寫單元測試,這些對象使用隨機數據填充字段。 他的理由是它提供了更廣泛的測試,因為它會測試很多不同的值,而正常測試只使用一個靜態值。

我給了他很多不同的原因,主要的原因是:

  • 隨機值意味著測試不是真正可重複的(這也意味著如果測試可以隨機失敗,它可以在構建服務器上這樣做並打破構建)
  • 如果它是一個隨機值,並且測試失敗,我們需要a)修復這個對象,並且b)強迫我們每次都測試這個值,所以我們知道它是有效的,但是因為它是隨機的,所以我們不知道這個值是什麼

另一位同事補充說:

  • 如果我正在測試異常,則隨機值不能確保測試以預期狀態結束
  • 隨機數據用於清除系統和加載測試,而不是用於單元測試

任何人都可以添加額外的理由,我可以給他讓他停止這樣做嗎?

(或者,這是一種寫入單元測試的可接受的方法,而且我和我的其他同事是錯誤的?)


Answers

如果您使用隨機輸入進行測試,則需要記錄輸入,以便查看這些值。 這樣,如果遇到某種邊緣情況,您可以編寫測試來重現它。 我聽說過人們不使用隨機輸入的相同原因,但是一旦您了解了用於特定測試運行的實際值,那麼它就不是什麼問題。

“任意”數據的概念作為一種表示不重要的東西的方式也非常有用。 我們有一些可以接受的驗收測試,因為有很多噪聲數據與手頭測試無關。


對於查看測試的人來說,一個好處是任意數據顯然並不重要。 我看過太多涉及數十個數據的測試,可能很難說出需要怎樣的方式以及恰恰如此。 例如,如果地址驗證方法使用特定的郵政編碼進行測試,並且所有其他數據都是隨機的,那麼您可以確定郵政編碼是唯一重要的部分。


有一個妥協。 你的同事實際上在談論某件事,但我認為他做錯了。 我不確定完全隨機測試是非常有用的,但它肯定不是無效的。

程序(或單元)規範是一個假設,即存在一些符合它的程序。 程序本身就是這個假設的證據。 單元測試應該是嘗試提供反證據,以反駁程序按規範工作。

現在,您可以手動編寫單元測試,但它確實是一項機械任務。 它可以自動化。 你所要做的就是編寫規範,一台機器可以產生大量的單元測試,試圖破壞你的代碼。

我不知道你在用什麼語言,但看到這裡:

Java http://functionaljava.org/

Scala(或Java) http://github.com/rickynils/scalacheck

Haskell http://www.cs.chalmers.se/~rjmh/QuickCheck/

.NET: http://blogs.msdn.com/dsyme/archive/2008/08/09/fscheck-0-2.aspx : http://blogs.msdn.com/dsyme/archive/2008/08/09/fscheck-0-2.aspx

這些工具將把你的格式規範作為輸入,並自動生成任意數量的單元測試,並自動生成數據。 他們使用“縮小”策略(可以調整)找到最簡單的測試用例來破壞代碼並確保它能很好地覆蓋邊緣案例。

快樂的測試!


如果你的傢伙沒有看到他是否已經修好了,你怎麼能再次進行測試? 即他失去了測試的可重複性。

雖然我認為在測試中扔掉大量隨機數據可能有一定的價值,正如其他回復中提到的,它在負載測試的標題下比其他任何內容都要多。 這幾乎是一種“希望測試”的做法。 我認為,實際上,你的傢伙根本就沒有想到他想要測試什麼,並且通過希望隨機性彌補這種缺乏想法最終會陷入一些神秘的錯誤。

所以我和他一起使用的論點是他很懶惰。 或者換一種說法,如果他沒有花時間理解他想要測試的東西,那可能表明他並不真正了解他正在編寫的代碼。


根據您的對象/應用程序,隨機數據將在負載測試中佔有一席之地。 我認為更重要的是使用明確測試數據邊界條件的數據。


我贊成隨機測試,並寫下它們。 但是,它們是否適合特定的構建環境以及它們應該包含哪些測試套件是一個更加細微的問題。

在本地運行(例如,在開箱即可過夜)隨機測試發現了明顯和模糊的錯誤。 這些晦澀難懂的東西已經足夠了,我認為隨機測試真的是唯一現實的測試方法。 作為一項測試,我通過隨機測試發現了一個難以發現的錯誤,並且有六位破解開發人員在其發生的地方查看函數(大約十幾行代碼)。 沒有人能夠檢測到它。

很多針對隨機數據的論點都是“測試不可重複”的口味。 然而,一個寫得很好的隨機化測試將捕獲用於啟動隨機化種子的種子並在失敗時輸出。 除了允許您手動重複測試外,您還可以輕鬆創建新測試,通過對該測試的種子進行硬編碼來測試特定問題。 當然,手動編寫一個涵蓋該案例的明確測試可能會更好,但懶惰有其優點,甚至可以讓您從失敗的種子中自動生成新的測試用例。

然而,你所做的一點我不能辯論,因為它打破了構建系統。 大多數構建和持續集成測試都希望測試每次都能做同樣的事情。 因此,隨機失敗的測試會產生混亂,隨機失敗並將手指指向無害的變化。

然後,解決方案是仍然運行隨機測試作為構建和CI測試的一部分,但運行固定種子,進行固定次數的迭代 。 因此,測試總是做同樣的事情,但仍探索一堆輸入空間(如果你運行它多次迭代)。

在本地,例如,在更改相關類時,您可以自由運行以獲得更多迭代或使用其他種子。 如果隨機化測試變得越來越流行,你甚至可以想像一個已知隨機的特定測試套件,它可以用不同的種子運行(因此隨著時間的推移覆蓋範圍越來越大),並且哪裡出現故障並不意味著同樣的事情作為確定性的CI系統(即,運行與代碼更改不是1:1關聯,所以當事情失敗時,您不會指向特定的更改)。

對於隨機測試有很多要說的,尤其是寫得很好的測試,所以不要太急於解僱他們!


你的同事正在做fuzz-testing ,儘管他不知道。 它們在服務器系統中特別有用。


我們今天剛碰到這個。 我想要偽隨機 (所以它看起來像壓縮音頻數據的大小)。 我想我也想要確定性 。 rand()在OSX上與Linux上不同。 除非我重播種子,否則它可能隨時改變。 所以我們將它改為確定性的,但仍然是隨機的:測試是可重複的,與使用罐裝數據一樣多(但更方便書寫)。

不是通過代碼路徑的一些隨機蠻力測試。 這就是區別:仍然是確定性的,仍然是可重複的,仍然使用看起來像真實輸入的數據來對複雜邏輯中的邊緣案例進行一系列有趣的檢查。 仍然是單元測試。

這仍然有資格是隨機的嗎? 我們來談談啤酒。 :-)


這種測試稱為猴測試 。 如果做得對,它可以從真正黑暗的角落吸出蟲子。

為了解決您對可重複性的擔憂:正確的方法是記錄失敗的測試條目,生成一個單元測試,該測試針對特定錯誤的整個家族 ; 並在單元測試中包含導致初始故障的一個特定輸入(來自隨機數據)。


我可以設想測試數據問題的三種解決方案:

  • 用固定數據進行測試
  • 用隨機數據進行測試
  • 生成一次隨機數據,然後將其用作固定數據

我會建議做所有上述 。 也就是說,寫出可重複的單元測試,用一些邊緣情況下用你的大腦計算出來,還有一些隨機數據只能產生一次。 然後寫一組隨機測試,你也運行。

不應期望隨機化測試能夠捕捉到可重複測試錯過的東西。 你的目標應該是覆蓋所有可重複測試的內容,並將隨機測試視為獎勵。 如果他們發現了什麼,它應該是你無法合理預測的東西; 一個真正的古怪。


在“ 美麗的代碼 ”一書中,有一章叫做“美麗的測試”,他在那里通過二進制搜索算法的測試策略。 其中一段被稱為“隨機測試行為”,其中他創建隨機數組以徹底測試算法。 您可以在谷歌圖書網頁上閱讀第95頁的在線書籍,但這是一本值得擁有的好書。

所以基本上這只是表明生成隨機數據進行測試是一個可行的選擇。


您能否生成一次隨機數據(我的意思是一次,而不是每次測試一次),然後在所有測試中使用它?

我可以肯定地看到創建隨機數據的價值,以測試那些你沒有想到的情況,但是你是對的,單元測試可以隨機通過或失敗是一件壞事。


如果你正在做TDD,那麼我會認為隨機數據是一個很好的方法。 如果你的測試是用常量編寫的,那麼你只能保證你的代碼適用於特定的值。 如果您的測試隨機失敗了構建服務器,則可能存在測試寫入問題。

隨機數據有助於確保將來的重構不​​會依賴於魔術常數。 畢竟,如果你的測試是你的文檔,那麼常量的存在意味著它只需要為那些常量工作?

我誇大,但我更喜歡隨機數據注入我的測試,作為“這個變量的價值不應該影響這個測試的​​結果”的信號。

我會說,如果你使用隨機變量,然後基於該變量分叉你的測試,那麼這是一種氣味。


我傾向於不測試私有方法。 有瘋狂。 就個人而言,我相信你應該只測試你公開的接口(包括受保護和內部方法)。







unit-testing tdd mocking