2013-02-20 55 views
7

我想了解更多關於JUnit和TDD,但我遇到了一些測試用例之間耦合的問題。減少測試用例之間的耦合

當我爲特定數據類型的API編寫測試用例時,說一個Deque<T>,如何限制測試用例之間的耦合?舉例來說,如果我的方法insertFirst(T item)編寫測試用例,它看起來非常簡單的假設,我應該能夠調用該方法正確初始化的對象之後,觸發兩件事情:

  1. Deque的大小對象應該增加一個
  2. 如果我隨後調用相應的T removeFirst()方法,它應該返回一個對初始調用插入的對象的引用。

但是,這至少會在我的兩個測試用例之間產生不希望的耦合,其中一個測試用例傳遞取決於另一個API方法的正確實現。例如,爲了通過這個測試用例,我需要一個正確的實現來檢查Deque中的項目數量以及刪除項目。如果我對任何一種方法的測試出於任何原因都是不正確或不完整的,那麼我對insertFirst方法的測試將自動受到懷疑。

避免這種情況的最佳做法是什麼?我以某種方式編寫測試用例是否錯誤?

回答

12

當爲一種方法編寫測試時,假設其餘的類都工作正常。如果你不做這個假設,唯一的結論將是每個班級的單個測試,大量的測試。這不是我們所做的。

您可以假設該類的其他部分正常工作,因爲也會對其他部分進行測試,以確保其正確性。
如果一部分工作不正常,測試將失敗,向您顯示某些內容不正確。
只要測試套件的測試失敗,就需要修復一個錯誤。你不能再做任何假設。

例子:

你只有三個方法,一個簡單的列表實現:

  1. 插入
  2. 刪除

你有三個測試:

  1. 試驗insert
    • 創建列表的實例(安排
    • insert項目(
    • 檢查count等於1(斷言
  2. 測試remove
    • 創建列表和insert項目的實例(安排
    • remove項目(
    • 檢查count等於0(斷言
  3. 試驗count
    • 創建列表實例和insert n項(安排
    • 檢索count
    • 檢查count等於n(斷言)現在

,如果上述任何測試失敗,你不能請確保您的班級的單個成員的正確性:

  • I如果第一次測試失敗,第三次測試也將失敗。第二個將通過,但實際上並沒有測試remove,因爲沒有東西可以刪除。
  • 如果第二次測試失敗,其他兩次測試仍然通過。不過,您不能確定insertcount是否正常工作,因爲如果三個成員中的任何一個不能正常工作,則第二個測試將失敗。
  • 如果第三次測試失敗,另外兩個最有可能也會失敗。

的失敗測試告訴你一件事,但:
根據失敗的測試,你經常可以扣除在誤差不得。
示例:如果只有第二個測試失敗,但不是第一個或第三個測試,則錯誤最可能在remove方法中。

+1

我明白你在說什麼,但是這不會創建方法的循環依賴嗎?爲了測試插入,我必須使用remove,並且爲了測試remove,我使用insert?不知何故,這似乎是錯的。這可能是因爲我對這些想法沒有足夠的經驗,我很快就會因爲不舒服而變得舒適。感謝您的澄清。 – crlane 2013-02-20 13:59:08

+1

@crlane:是的,它創建了某種循環依賴。這就是爲什麼即使只有一次測試失敗,你也無法說出你的課程*的任何事情。只有*全部*測試通過,你才知道一切都按預期工作。 – 2013-02-20 14:00:20

+0

@crlane:請參閱具體示例的更新答案。 – 2013-02-20 14:12:32

4

將單元測試視爲測試特定功能而不是特定方法通常會更有成效。任何給定的測試都將檢查某些方法集合是否正確地工作以實現作爲測試主題的功能,而設計良好的測試集中的失敗模式將傾向於告訴您哪種方法相當快地中斷。

一個好的測試集合傾向於自然地脫離TDD;這是使技術如此強大的一件事情。如果我正在編寫Deque,我寫的測試將傾向於如下,通常按此順序呈現。

  1. empty_Deque_isEmpty - 實施isEmpty總是返回true
  2. non_empty_Deque_isntEmpty - 實施insertFirst使isEmpty實例變量假
  3. re_emptied_Deque_isEmpty - 改變由isEmpty使用實例變量是一個數字,響應insertFirstremoveFirst
  4. is_empty_Deque_size_correct - 實施size總是返回0
  5. is_nonempty_Deque_size_correct - 添加實例變量來跟蹤大小;意識到它正在做同樣的事情isEmpty需要;重構
  6. is_re_emptied_Deque_size_correct - 有測試只是通過,因爲我們做了什麼,使5發生
  7. does_removing_from_empty_Deque_throw - removeFirst需要做其他事情之前,要檢查size
  8. is_inserted_item_returned - insertFirstremoveFirst現在填充T實例變量
  9. is_inserted_item_returned_from_end - 添加removeLast那就是removeFirst複印件;重構
  10. is_rear_inserted_item_returned - 添加insertLast那份insertFirst;重構
  11. are_all_inserted_items_returned - 變化insertFirstremoveFirst作用於SomeKindOfCollection<T>;使不檢查檢索
  12. does_removeFirst_retrieve_items_in_correct_order的順序點 - 插入兩件事情,確保第二個是由removeFirst返回。可能已經是真的了。
  13. does_removeLast_retrieve_items_in_correct_order - 同上,用於removeLast,除了相當肯定不是已通過。

這是一大堆測試,但是當你通過他們看,你應該注意到這種模式。這些測試都不是真正的「count的測試」或「removeFirst的測試」。但是,當我們經歷這些時間之後,整個界面正在運行,並且所有內部必需的界面都已經被開發出來。某些測試依賴於多種方法,如果該方法失敗,它們將全部中斷。但是休息模式對於確定錯誤的位置往往非常有幫助。

另外有趣的是,我們可以做多少次這樣的測試,而不需要承諾在對象中實際存在一個集合,這表明這組測試可以被分解成更通用的測試套件,在開發PriorityQueue時很有用。

+0

+1我完全同意「測試特定功能而不是特定方法」。我雖然也想把它帶入我的答案,但我感覺它會壓倒OP。很高興你提起它! – 2013-02-21 21:52:14