2010-11-13 10 views
47

在C++中,我經常將單元測試類作爲我正在測試的類的一位朋友。我這樣做是因爲我有時覺得需要爲私有方法編寫單元測試,或者我想訪問某個私有成員,這樣我可以更輕鬆地設置對象的狀態,以便測試它。對我來說,這有助於保持封裝和抽象,因爲我不修改該類的公共或受保護的接口。將單元測試作爲正在測試的課程的朋友有什麼問題?

如果我買一個第三方庫,我不希望它的公共接口和一幫公有方法被污染,我不需要知道,只是因爲廠商要進行單元測試!

也不想擔心一堆protected成員,我並不需要知道如果我從一個類繼承的。

這就是爲什麼我說它保留了抽象和封裝。

在我的新工作,他們皺眉反對使用友元類甚至是單元測試。他們說,因爲班級不應該「知道」任何有關測試的內容,而且你不希望班級與測試緊密結合。

有人可以向我解釋這些原因,以便我可以更好地理解?我只是不明白爲什麼使用朋友進行單元測試是不好的。

+0

我想知道,如果他們也推出了一些替代方案? – 2010-11-13 06:36:43

+0

替代方法找到了一種方法來調用使用公共接口的方法;使其受到保護並擴大課程範圍; – cchampion 2010-11-13 06:38:46

+3

不想讓測試緊密耦合到它測試的代碼看起來好像意圖出錯了。 「正常」代碼之間的緊密耦合通常是要避免的,但測試將與他們按定義測試的代碼結合使用。 – 2010-11-13 06:58:07

回答

51

理想情況下,你根本不需要單獨測試私有方法。你們班的所有消費者都應該關心的是公共接口,所以這就是你應該測試的。如果一個私有方法有一個錯誤,它應該被一個單元測試所捕獲,該單元測試調用該類的一些公共方法,最終最終調用這個錯誤的私有方法。如果一個bug漏過了,這表明你的測試用例沒有完全反映你希望你的課程實施的合同。這個問題的解決方案几乎可以肯定地用公共方法進行更仔細的測試,而不是讓你的測試用例深入到類的實現細節中。

再次,這是理想的情況。在現實世界中,情況可能並不總是那麼明顯,並且讓單元測試課成爲它所測試類的朋友可能是可以接受的,甚至是可取的。不過,這可能不是你想要的。如果它似乎經常出現,那可能表明你的課程太大和/或執行了太多的任務。如果是這樣,通過將複雜的私有方法集重構爲單獨的類來進一步細分它們應該有助於消除單元測試以瞭解實現細節的需要。

+10

+1表示過於私密的方法通常可以在另一個類中公開。 – Philipp 2010-11-13 07:47:28

+6

我不同意這一點。如果你正在實現一個複雜的算法,並且想要以小步驟來分解它(這在這個算法的實現之外是完全沒有用的),那麼想把這些步驟作爲一個私有的實現細節保持不變,代碼中沒有其他對象應該能夠訪問,並且仍然希望檢查每個子步驟在給定正確輸入的情況下返回正確的結果。 – 2017-04-01 13:37:27

+1

+ 1 @ Jean-MichaëlCelerier。你可以有一個很長的**函數,這個函數是公開的,或者將它分解成邏輯部分。但邏輯部分不必公開。但是,他們需要進行測試。防爆。變形的圖像變形。如果沒有使用庫是一個很長的代碼,如果沒有分解成部分 – navderm 2017-04-26 15:29:13

4

像BCAT建議,儘可能的,你需要找到使用公共接口本身的錯誤。但是如果你想做一些事情,比如打印私有變量和比較預期結果等(對於開發人員來說很容易調試問題很有幫助),那麼你可以將UnitTest類作爲朋友類來測試。但是您可能需要將其添加到下面的宏下。

class Myclass 
{ 
#if defined(UNIT_TEST) 
    friend class UnitTest; 
#endif 
}; 

僅當需要進行單元測試時啓用標誌UNIT_TEST。 對於其他版本,您需要禁用此標誌。

12

你應該考慮有不同的風格和方法來測試:Black box testing只測試公共接口(治療類作爲一個黑盒子)。如果你有一個抽象基類,你甚至可以對所有實現使用相同的測試。

如果使用White box testing,你甚至可以看看實現的細節。不僅關於一個類有哪些私有方法,而且包含了什麼樣的條件語句(即,如果你想增加你的條件覆蓋率,因爲你知道條件很難編碼)。在白盒測試中,你肯定有類/實現和測試之間的「高度耦合」,這是必要的,因爲你想測試實現而不是接口。

由於BCAT指出,它往往有助於使用成分多,但更小的類,而不是許多私有方法。這簡化了白盒測試,因爲您可以更輕鬆地指定測試用例以獲得良好的測試覆蓋率。

1

通常你只有讓你可以自由地重新設計和重構執行測試的公共接口。爲私人成員添加測試用例定義了對您的課程實施的要求和限制。

4

在很多情況下,我沒有看到使用朋友單元測試課的任何錯誤。是的,將一個大班級分解成更小的班級有時是更好的方式。我認爲人們對使用friend關鍵字這樣的東西有點太倉促 - 它可能不是理想的面向對象的設計,但如果這是我真正需要的,我可以犧牲一點點理想主義以獲得更好的測試覆蓋率。

0

使想要測試的功能得到保護。 現在在你的單元測試文件中,創建一個派生類。 創建調用您的受測試類受保護函數的公共包裝函數。

+0

朋友有更好的封裝比保護。通過對成員進行保護,您可以訪問所有繼承類,並且隱含地說您將永遠不會更改它們,因爲它們是繼承接口的一部分。通過把這些成員私人化,爲一個班級提供友誼,你只與這個班級有一個隱含的合同。如果這個類是一個單元測試類,那麼這個合約就沒有約束力。 – paercebal 2013-06-14 15:32:36

9

我覺得BCAT給了一個很好的答案,但我想闡述的是他暗示

在現實世界中的特殊情況,事情並不總是那麼清晰,並具有 單元測試類可能是可接受的,或者甚至是可取的,它測試的類可能是 的朋友。

我在一家擁有大量遺留代碼庫的公司工作,這有兩個問題,這兩個問題都有助於進行朋友單元測試。

  • 我們忍受着大量需要重構的大型函數和類,但爲了重構它,有助於進行測試。
  • 我們的許多代碼都依賴於數據庫訪問,由於各種原因,不應將其引入單元測試。

在某些情況下,嘲諷是減輕後者的問題是有用的,但很多時候這只是導致uneccessarily複雜的設計(其中,否則將需要none類heirarchies),而人們可以非常簡單地重構代碼,在以下方式:

class Foo{ 
public: 
    some_db_accessing_method(){ 
     // some line(s) of code with db dependance. 

     // a bunch of code which is the real meat of the function 

     // maybe a little more db access. 
    } 
} 

現在我們有了函數的肉需要重構的情況,所以我們想要一個單元測試。它不應該公開暴露。現在,在這種情況下可以使用一種稱爲嘲笑的美妙技術,但事實是,在這種情況下,模擬是矯枉過正的。這需要我用不必要的層面來增加設計的複雜性。

一個更爲務實的做法是做這樣的事情:

class Foo{ 
public: 
    some_db_accessing_method(){ 
     // db code as before 
     unit_testable_meat(data_we_got_from_db); 
     // maybe more db code.  
} 
private: 
    unit_testable_meat(...); 
} 

後者給了我所有的我從單元測試所需要的利益,包括給我寶貴的安全網,捕捉時產生的錯誤我重構了肉中的代碼。爲了進行單元測試,我必須要有一個UnitTest類的朋友,但我強烈認爲,這比一個無用的代碼規範要好得多,只是爲了讓我使用Mock。

我認爲這應該成爲一個成語,我認爲這是一個合適的,實用的解決方案來提高單元測試的投資回報率。