2009-10-01 101 views
14

我目前正在參與使用C#進行開發 - 這裏有一些背景: 我們使用我們的客戶端應用程序實現了MVP,我們有一個圈規定,沒有任何方法的圈複雜度應大於5. 這導致了很多小型的私人方法,這些方法通常對一件事負責。單元測試專用代碼

我的問題是關於單元測試類:

測試通過的公共方法私有實現是所有罰款...我沒有貫徹這是一個問題。

但是......怎麼樣了以下情況:

例1處理異步數據retrival請求的結果(回調方法不應該純粹是爲了測試是公開的)

例2.的事件,其確實的操作(如更新查看標籤的文本 - 傻的例子,我知道...)

例3您正在使用第三方框架,該框架允許您通過覆蓋受保護的虛擬方法來進行擴展(從公共方法到這些虛擬方法的路徑通常被視爲黑盒編程,並且將具有框架提供的各種依賴關係不想知道)

上面的例子在我看來不是由於糟糕的設計。 他們似乎也不會成爲單獨進行單獨測試的候選人,因爲這樣的方法會失去其背景。

對此有沒有人有任何想法?

乾杯, 傑森

編輯: 我不認爲我是在我原來的問題很清楚 - 我可以測試使用訪問私有方法和使用TypeMock模擬出呼叫/方法。這不是問題。問題在於測試不需要公開的東西,或不能公開的東西。

我不想爲了測試而公開代碼,因爲它可能引入安全漏洞(只發佈一個接口來隱藏這個不是一個選項,因爲任何人都可以將對象轉換回原始類型並獲得訪問的東西,我不希望他們)

重構到另一個類進行測試的代碼很好 - 但可能會丟失上下文。我一直認爲有一個不好的習慣,那就是有'輔助'類,它可以包含一堆沒有特定上下文的代碼 - (在這裏思考SRP)。我真的不認爲這也適用於事件處理程序。

我很高興被證明是錯誤的 - 我只是不確定如何測試這個功能!我一直認爲,如果它可以打破或改變 - 測試它。

乾杯,傑森

+0

你將使用什麼隔離框架? – 2009-10-01 21:01:38

+0

我正在使用Typemock隔離器。嘲笑方法調用很容易 - 我真的不喜歡這樣做,因爲它會改變被測試類的行爲! – Jason 2009-10-03 10:25:51

+1

所有這些讓我希望我可以從頭開始寫自己的框架,所以我可以設計這些問題開始! – Jason 2009-10-03 11:02:13

回答

3

在我所有的單元測試,我從來沒有打擾測試private功能。我通常只測試public函數。這與黑盒測試方法一致。

你是正確的,你真的,除非你暴露私有類無法測試私有函數。

如果你的「測試獨立的類」是在同一個組件,您可以選擇使用內部,而不是私人的。這將內部方法暴露給您的代碼,但他們的方法不會在您的程序集中訪問代碼。

編輯:搜索這個主題我遇到了this question。答案最多的答案與我的答覆類似。

+2

啊,但你實際上可以測試私人課程:)。應該在「但我應該嗎?」下提交,但實際上可能。並且有些情況下我發現它很有用。 – Matt 2009-10-01 21:26:51

+0

+1 Matt:同上。 – 2009-10-02 02:01:54

0

我會承認,當最近寫單元測試的C#我發現很多我知道Java的招數並沒有真正應用(在我的情況下,它是測試的內部類)。

例如1,如果你能假/嘲笑數據檢索處理程序,你可以得到通過僞造的訪問回調。 (我知道大多數使用回調的其他語言也往往不會使它們變爲私有的)。

例如2我會查看觸發事件來測試處理程序。

示例3是其他語言中存在的模板模式示例。我已經看到了兩種方法可以做到這一點:

  1. 測試全班反正(或至少它的相關件)。這在抽象基類自帶測試或整體類不太複雜的情況下尤其適用。例如,在Java中我可能會這樣做,如果我正在編寫AbstractList的擴展。如果模板模式是通過重構生成的,則也可能是這種情況。

  2. 額外掛鉤,允許直接調用保護方法再次擴展類。

13

正如克里斯所說的,標準做法是僅對公共方法進行單元測試。這是因爲,作爲該對象的消費者,您只關心公共可用的內容。而且,從理論上講,用邊緣案例進行適當的單元測試將充分利用它們所擁有的所有私有方法依賴性。這就是說,我發現有幾次直接對私有方法編寫單元測試是非常有用的,並且通過單元測試解釋一些更復雜的場景或邊緣情況可能是最簡單的遇到。

如果是這樣,您仍然可以使用反射來調用私有方法。

MyClass obj = new MyClass(); 
MethodInfo methodInfo = obj.GetType().GetMethod("MethodName", BindingFlags.Instance | BindingFlags.NonPublic); 
object result = methodInfo.Invoke(obj, new object[] { "asdf", 1, 2 }); 
// assert your expected result against the one above 
+0

+1,這很漂亮。我從來不知道反思可以做到這一點。 – Chris 2009-10-01 21:26:03

+0

你也可以使用私人訪問器和(可選)傳遞一個私有對象來包裝你的真實實例,這是一個非常乾淨的事情。我認爲這也可以防止你在簽名時遇到任何反射問題(不要引用我的話 - 我從來沒有這樣做過!!) – Jason 2009-10-03 10:35:43

7

我們有一個圈規則,規定 沒有方法應該有一個 圈複雜度大於5

我喜歡這個規則更大。

問題是私有方法是實現細節。他們可能會改變/重構。你想測試公共接口。

如果您擁有複雜邏輯的私有方法,請考慮將它們重構爲單獨的類。這也可以幫助保持圈複雜度下降。另一個選擇是使方法內部並使用InternalsVisibleTo(在Chris的答案中的一個鏈接中提到)。

當您在私有方法中引用外部依賴項時,捕獲往往會進入。在大多數情況下,您可以使用諸如Dependency Injection等技術來分離您的課程。對於第三方框架的例子,這可能很困難。我會首先嚐試重構設計以分離第三方依賴關係。如果這不可行,請考慮使用Typemock Isolator。我沒有使用它,但它的主要特點是能夠「模擬」私人,靜態等方法。

類是黑匣子。以這種方式測試它們。

編輯:我會嘗試迴應傑森的評論我的答案和編輯原始問題。首先,我認爲SRP更多的類,不離他們。是的,最好避免瑞士軍隊的助手課程。但是如何設計一個類來處理異步操作呢?還是數據檢索課?原始課程的責任是這些部分,還是可以分開?

例如,假設您將此邏輯移至另一個類(可能是內部的)。該類實現Asynchronous Design Pattern,允許調用方選擇是同步還是異步調用該方法。單元測試或集成測試是針對同步方法編寫的。異步調用使用標準模式,複雜度低;我們不測試這些(除了在驗收測試中)。如果異步類是內部的,使用InternalsVisibleTo來測試它。

+2

謝謝你的答案 - 我應該提到我使用TypeMock Isolator和嘲諷類,方法類不是問題。私人代碼無法解決問題。什麼是問題是(舉例1)如果我嘲笑執行數據檢索的類 - 我不能再調用回調函數。 (示例2)我無法測試私人事件處理程序,而無需深入到類的私有方法中。 – Jason 2009-10-03 10:39:30

4

實在是隻有兩個需要考慮的情況:

  1. 專用代碼被調用時,直接或間接地從公共代碼和
  2. 專用代碼是公共代碼調用。

在第一種情況下,專用代碼會自動被其行使該調用私有代碼市民代碼測試的測試,因此沒有必要測試專用代碼。在第二種情況下,私人代碼根本無法被調用,因此應刪除,未經測試。

Ergo:沒有必要明確地測試私有代碼。

請注意,當你做TDD時,它是不可能爲未經測試的私有代碼甚至存在。因爲當你執行TDD時,私有代碼可以出現的唯一方式是通過Extract {Method | Class | ...}從公共代碼重構。根據定義,重構是保持行爲並因此保持測試覆蓋。公開代碼的唯一出現方式就是失敗測試的結果。如果公共代碼由於失敗的測試而只能顯示爲已經測試過的代碼,並且私有代碼只能通過保留行爲的重構從公共代碼中提取出來,那麼未經測試的私有代碼永遠不會出現。

+0

當你考慮我給出的第一個或第二個例子時,我不相信這是真的。您斷言專用代碼只能作爲重構公共代碼的直接結果存在 - 但這並不能解釋如何測試事件處理程序或異步回調的代碼。這些是我所遇到的真正的兩個問題,無法讓我的頭腦! – Jason 2009-10-03 10:32:14

2

從TDD人誰已經在C#中被敲打周圍的幾個要點:

1)如果你面向接口編程,然後一類是不是在接口的任何方法是有效的私有。您可能會發現這是提升可測試性的更好方法,也是更好的使用接口的方法。測試這些公衆成員。

2)這些小幫手方法可能更適合屬於某個其他類。尋找功能羨慕。作爲原始類的私人成員(你發現它)可能是不合理的,它可能是它所羨慕的類的合理的公共方法。在新班級中作爲公衆成員進行測試。

3)如果你檢查一些小的私有方法,你可能會發現它們具有凝聚力。它們可能代表了與原始類別不同的​​較小類別的興趣。如果是這樣,那個類可以擁有所有的公共方法,但是可以作爲原始類的私有成員,或者可以在函數中創建和銷燬。在新班級中作爲公衆成員進行測試。

4)您可以從原始派生一個「Testable」類,在這個類中創建一個新的公用方法,除了調用舊的私有方法外什麼也不做。可測試類是測試框架的一部分,而不是生產代碼的一部分,所以對它有特殊的訪問權限是很酷的。在測試框架中進行測試,就好像它是公開的。

所有這些都使得對當前使用私有助手方法的方法進行測試變得非常簡單,而不會影響智能感知的工作方式。

0

不要測試私人代碼,否則在稍後重構時會後悔。然後,你會像Joel一樣做博客,關於TDD是如何工作太多的,因爲你不得不用代碼重構你的測試。

有技術(嘲笑,存根)做適當的黑匣子測試。看看他們。

1

有幾個人迴應說,不應該直接測試私有方法,或者應該將其移至另一個類。雖然我認爲這很好,但有時它不值得。雖然我原則上同意這一點,但我發現這是爲了節省時間而不會產生負面影響的那些規則之一。如果函數很小/簡單,那麼創建另一個類和測試類的開銷就會過大。我會公開這些私有方法,但不會將它們添加到接口中。這種方式的消費者(誰應該只通過我的IoC庫獲得接口)不會意外使用它們,但它們可用於測試。

現在在回調的情況下,這是一個很好的例子,其中公開私有屬性可以使測試更容易編寫和維護。例如,如果A類將回調傳遞給B類,我將使該回調成爲A類的公共屬性.A類的一個測試使用B的存根實現來記錄傳入的回調。然後,測試將驗證在適當的條件下回調被傳遞給B.然後,A類的單獨測試可以直接調用回調函數,驗證它具有適當的副作用。

我認爲這種方法非常適用於驗證異步行爲,我一直在做一些JavaScript測試和一些lua測試。好處是我有兩個小的簡單測試(驗證回調的設置,驗證它的行爲如預期)。如果您試圖保持回調私密,那麼驗證回調行爲的測試有更多的設置要做,並且該設置將與應該在其他測試中的行爲重疊。糟糕的聯結。

我知道,它不漂亮,但我認爲它運作良好。

+0

對於你的第一個建議 - 我會不願意這樣做,因爲你只需要簡單的點擊就可以在你的應用程序中引入一個巨大的安全保留。 您對處理回調的建議大概就像我使用異步方法測試一樣......我真的結束了將代碼更改回私人並更改我的測試以最終測試使用訪問器的私有實現,因爲我沒有做到這一點,不想讓代碼公開。這樣做只是有點「氣味」:( – Jason 2009-10-03 10:43:07

+0

你可以擴展與鑄造相關的安全漏洞嗎? – 2009-10-05 00:19:14

+0

嗨,弗蘭克,使用一點反思,它不是很難找出背後的實際類型一個接口,然後可以將實現該接口的對象轉換回原來的類型,並訪問通過接口無法訪問的公共方法。據我所知,(在這種情況下)一個接口的點不是安全的 - 它是向開發者指示類(或多態)的正確用法 – Jason 2009-10-05 21:54:09

0

這是一個在引入測試時很早就出現的問題。解決此問題的最佳方法是黑盒測試(如上所述)並遵循單一責任原則。如果你的每個類只有一個理由要改變,他們應該很容易測試他們的行爲,而不必使用他們的私有方法。

SRP - wikipedia/pdf

這也導致了更強大和適應性代碼爲單一職責原則實際上只是說,你的類應該具有高cohesion

+0

在這種情況下,您將如何解決示例1? – 2009-10-02 22:19:10

+0

我不得不看到代碼,但馬上我會說文件檢索本身不需要知道它是異步的。您應該分別實現該功能並將其擴展爲支持異步傳輸。在這種情況下,我假設您正在使用完整的異步請求並在回調中處理結果。這兩者已經明確分開。一個發送,另一個接收。測試發送。測試接收。一起測試。更多的事情將會公開,但不在您現有的情況下。 – 2009-10-02 22:48:33

+0

這裏是我如何實現你的建議(請糾正我,如果我錯了!)我有一個類(C1),它可以以異步方式獲取數據。我的調用類(C2)調用數據檢索類。調用類不知道異步調用需要多長時間(因爲它是在另一個線程上定義的)。數據檢索類發出信號通知它已檢索到數據。 C2然後從C1中提取數據。那麼如何在不引入競爭條件的情況下對其進行全面測試?測試發送和測試接收是否正常 - 但我如何一起測試? – Jason 2009-10-03 10:51:24

2

這裏有一些很好的答案,我基本上同意重複建議發芽新班。對於示例3,然而,有一個偷偷摸摸的,簡單的方法:

例3.您使用的是第三方 框架,它可以讓你通過重寫受保護的虛擬 方法(路徑從公共 延長 這些虛擬方法方法 通常被視爲黑盒 編程,將有各種 依賴關係框架 提供你不想知道 約)

假設MyClass擴展FrameworkClass。讓MyTestableClass擴展MyClass,然後在MyTestableClass中提供公共方法,公開您需要的MyClass的受保護方法。這不是一個很好的做法 - 這對於糟糕的設計是一種促成因素 - 但有時很有用,而且非常簡單。

0

在C#中,你可以在AssemblyInfo.cs中使用屬性:

[assembly: InternalsVisibleTo("Worker.Tests")] 

只需使用內部標記您的私有方法,測試項目將仍然可以看到該方法。簡單!你可以保持封裝並進行測試,而不需要所有的TDD廢話。

+0

下次,請將問題標記爲重複,而不是發佈多個相同的答案。謝謝! – Rob 2016-12-29 05:26:06