簡體   English   中英

如何對代碼進行分解以簡化可測試性?

[英]How do I factor code to ease testability?

我正在學習單元測試,並想知道如何編寫可測試的代碼。 但是,我不確定如何在不使其復雜的情況下編寫可測試代碼。 我將采用着名的汽車和發動機問題來描述問題。

class Car
{
private:
   Engine m_engine;

public:
   Car();
   // Rest of the car
}

我提出了以下解決方案,以使上述代碼可測試。

  1. 更改Car的構造函數以將Engine作為參數。 然后模擬引擎並進行測試。 但是,如果我沒有不同類型的引擎,那么參數化構造函數似乎是不合適的,只是為了使它可測試。

  2. 使用setter然后將模擬引擎傳遞給setter。 與上述相同的流程。

  3. 首先測試引擎,然后使用經過驗證的引擎(或使用存根引擎)測試汽車。

我必須在代碼上測試哪些替代方案? 每種方法的優點和缺點是什么?

從不同的(測試驅動開發)觀點來看:易於測試的代碼易於使用。 編寫單元測試實際上是在測試代碼的“公共接口”。 如果它很難測試,那是因為你在那里有一些依賴,這使得它很難。 你真的需要一個遏制關系,還是一個聯想關系會更有意義?

在你的情況下,我個人認為在構造函數中傳遞Engine會更容易測試,所以我會像你的建議#1那樣重構構造函數。 您可以在一個測試套件中測試引擎,並提供一個模擬引擎來測試另一個測試套件中的Car。 現在測試它很簡單,這意味着界面易於使用。 這是好事。

現在考慮如何在實際項目中使用該實現。 您將創建一個CarFactory類,工廠將創建一個引擎並將其放入Car中,然后再將其交付給您。 (還要注意這最終如何更接近地模擬汽車,發動機和工廠的真實世界,但我離題了。)

因此,TDD的答案是重構代碼以在構造函數上獲取Engine指針。

如果您只有一個引擎類型,為什么要嘗試將其作為新對象? 如果您不打算交換引擎,請不要創建另一個抽象層。 只需將發動機作為汽車的一部分。

您可能正在分解以降低復雜性,而不是重用組件。 好決定。 在這種情況下,我會說3是你最好的選擇 - 驗證你的低級組件,然后使用調用較低級別對象的更高級代碼。

實際上,Engine更像是數據庫。 並且您將需要更改構造函數以使用不同的數據庫(出於測試原因或其他原因),但您可以暫時擱置該謊言。

正如其名稱所暗示的,單元測試是關於測試單元以確認它們按照規定工作。 這意味着,您應該單獨測試引擎。

系統測試或集成測試是關於測試它們是否正確“粘合”在一起。

當然,它比這更復雜,但它應該指向正確的方向。

選項1通常是正確的方式。

通過完全控制您給汽車的發動機,您可以很好地測試汽車。

您可以使用引擎為汽車提供的所有不同輸出,更輕松地測試汽車的行為方式。 您還可以確保汽車對發動機進行適當的調用。

在構造函數中使用它可以清楚地表明Car依賴於引擎來工作。 將它與依賴注入框架一起使用,構造函數問題根本不是問題。

真正的問題是,有哪些要求? 如果目標是簡單地實現“汽車”對象,那么它甚至不需要引擎。

測試應始終與要求相關。 任何對象模型都將是對現實的一些概括,因此問題在於需要表示哪些方面。

一旦你的需求下降,那么你應該在通用的高級別上編寫測試。 然后,OO設計應該以這樣的方式完成,即可以實現這些測試。

Misko Hevery經常寫這個話題。 這是 2009年10月的演示文稿 。他認為依賴圖應該在構造函數中是顯式的。

參數化構造函數以使其可測試似乎是不合適的

我認為這是一個引人入勝的評論。 在什么樣的條件下,包括可測性作為設計的一部分是不合適的? 犧牲可測試性的正確性顯然是錯誤的,盡管在實踐中我從未見過這種選擇被迫。 犧牲可測性的性能......也許在某些特定情況下。 犧牲代碼一致性? 我個人寧願改變編碼標准,並逐步將遺留代碼納入合規性。

C ++的一個可能性是使用友誼:

class Car
{
private: //for unit testing
    friend class TestCar; //this class is the unit test suite for the Car class
    Car(Engine* mockEngine); //this constructor is only used by the TestCar class
private:
    Engine* m_engine;
public:
    Car();
    // Rest of the car
};

第二種可能性是構造函數的實現使用全局/靜態方法,例如如下所示,您可以通過一些配置文件或通過鏈接(可能是動態鏈接)到此不同版本來更改此方法的實現方法:

Car::Car()
{
    m_engine = Engine::create();
}

我主張允許(通過構造函數或屬性)將Engine的實現添加到Car(最好是IEngine)。

汽車測試實際上並不關心引擎的作用,只要它能夠正確響應調用引擎的結果。 然后,使用假引擎進行測試,以便您可以控制發送到汽車的信號,您應該好好去。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM