2017-08-12 110 views
2

說你有3個功能,泛函,functionB和functionC單元測試複合功能

functionC依賴泛函和functionB

functionA(a) { 
    return a 
} 
functionB(b) { 
    return b 
} 
functionC(a, b){ 
    return functionA(a) + functionB(b); 
} 

現在,這顯然是一個超級簡單的例子..但什麼是正確的測試functionC的方法?如果我已經在測試函數A和函數B,並且它們通過則不會測試函數C,因爲它將依賴於函數A和函數B返回,所以測試函數C不會超過單元測試。

+0

是否有過在那裏你任何功能*不*調用其他功能?即使它只是標準庫函數?這本身不是一個標準。 – deceze

+0

我注意到你還沒有接受任何答案。請考慮這樣做......或者讓我知道是否有什麼我可以添加,以使我的答案接受值得。 – GhostCat

回答

4

對於您將關注的前兩個函數他們的公共合同 - 您可以根據需要編寫儘可能多的測試,以確保針對不同情況的所有結果都符合預期。

但是對於第三個函數來說,理解函數應該調用其他兩個函數可能就足夠了。所以你可能需要更少的測試。您不需要再次測試測試A和B所需的所有情況。您只需要驗證C負責的預期「管道」。

+0

短而精的答案總是更好:) – davidxxx

4

在我看來你的測試應該不知道 functionC使用functionA和functionB。通常情況下,您會創建自動測試,以支持更改(代碼維護)。如果你改變C的實現呢?所有的函數C的測試也變得無效,這是不必要的和危險的,因爲這意味着,重構者必須理解所有的測試。即使他/她相信,他/她也沒有改變合同。如果你有一個很好的測試,他/她爲什麼要這樣做?因此,functionC的公共契約將被完全測試!

還有一個進一步的危險,如果測試知道太多關於sut(functionC)的內部工作,他們傾向於重新實現裏面的代碼。所以執行的相同(可能是錯誤的)代碼會檢查實現是否正確。 只是一個例子:你將如何實現functionC的(白盒)測試。模擬函數A和函數B,看看是否產生了模擬結果的總和。這對測試覆蓋率(kpi ??)很有用,但也可能會造成誤導。

但是如何測試功能A和功能B的功能兩次的高額外努力。如果是這樣的話,那麼很可能重用測試代碼很容易,如果不可能重用,我認爲這更證實了我之前的陳述。

+0

如果functionA調用另一個有副作用的函數(如數據庫更新)會怎麼樣?當測試functionC時,你會存儲具有副作用的a-tree的最後一個函數嗎?如果是這樣,那麼在使用函數A的所有測試函數中都需要重複這些操作?或者你會存儲函數A並返回一個模擬以避免副作用? – henit

+0

@henit我不會避免副作用,但也要檢查它。每個測試應該從一個空的數據庫開始,或者至少從相同的數據庫狀態開始。否則,預計會出現閃爍測試。這必須是測試基礎架構的一部分。 – aschoerk

+0

如果副作用超出測試架構,該怎麼辦?像http呼叫到外部api – henit

1

GhostCat的答案很簡單,很好,專注於必備。
我會詳細介紹一些其他要考慮的問題,特別是重構問題。


單元測試集中在API

的類API(公共函數)必須被單元測試。
如果這3個功能是公開的,每個功能都必須進行測試。

此外,單元測試不關注實現,但預期的行爲。
今天,複合功能添加個別功能結果,明天它可以減少它們或其他任何東西。
測試C()複合函數並不意味着再次測試A()B()的所有方案,這意味着測試C()的預期行爲。

在某些情況下,與單個函數集成的單元測試複合函數不會在單個函數中產生很多重複。
在其他情況下,它的確如此。我將在下一點介紹它。


實施例,其中測試所述C()複合函數可能會導致在測試中的複製的關注。

假設A()函數接受兩個整數:

function A(int a, int b){ ...} 

它具有關於輸入參數以下約束:

  • 它們必須是> = 0
  • 他們是低於100
  • 其總和已經低於100

如果其中一個不受尊重,則會引發異常。 在A()單元測試中,我們將測試這些場景中的每一個。每一個可能在不同的測試案例:

@Test 
void A_throws_exception_when_one_of_params_is_not_superior_or_equal_to_0(){ 
    ... 
} 

@Test(expected = InvalidParamException.class); 
void A_throws_exception_when_one_of_params_is_not_inferior_to_100(){ 
    ... 
} 

@Test(expected = InvalidParamException.class); 
void A_throws_exception_when_params_sum_is_not_inferior_to_100(){ 
    ... 
} 

除了錯誤的情況下,我們也可以爲A()功能多正常情況下根據傳遞的參數。

假設B()函數也有多個名義和錯誤的情況。

那麼集合它們的C()的單元測試呢?
您當然不應該重新測試這些案例中的每一個。這是很多重複的情況,而且它將通過跨越兩種功能的情況而具有更多的組合。
下一步介紹如何防止重複。


可能的重構來改進設計並降低複合函數的單元測試的重複

當你寫的複合函數,你應該知道的第一件事就是是否複合函數應該不在位於特定組件中。

composite component -> unitary component(s) 

將它們分開可能會改善總體設計並給予組件更具體的責任。
此外,它還提供了一種自然的方法來減少複合組件的單元測試中的重複。
事實上,如果需要,您可以使用存根/模擬酉分量行爲,而無需爲它們創建詳細的固定裝置。
複合組件單元測試可以專注於複合組件行爲。

所以在我們前面的例子,而不是測試的A()B()所有的情況下,我們的單元測試的C()功能,我們可以存根或嘲笑A()B(),以使他們的行爲預期的C()場景。

例如,對於與相關A()B()錯誤情況下,C()測試場景中,我們並不需要重複每個A()B()情景案例:

@Test(expected = InvalidParamException.class); 
void C_throws_exception_when_a_param_is_invalid(){ 
    when(A(any,any)).thenThrow(new InvalidParamException()); 
    C(); 
} 

@Test(expected = InvalidParamException.class); 
void C_throws_exception_when_b_param_is_invalid(){ 
    when(B(any,any)).thenThrow(new InvalidParamException()); 
    C(); 
} 
+0

冗長的回答也很好。一切取決於作者:-) – GhostCat

+0

@GhostCat正確:)我通過查看你最近的歷史偶然發現這個老問題。很有意思 !感謝您留下一些東西來添加:) – davidxxx