簡體   English   中英

是否正在創建用於測試不良做法的私有構造函數?

[英]Is creating a private constructor for testing bad practice?

我遇到了一些java代碼,其中公共構造函數調用一個包私有構造函數,其中包含一堆new運算符來創建新對象。

public class Thing {

    //public
    public Thing(String param1, int paramm2) {
        this(param1, param2, new Dependency1(), new Dependency2());
    }

    //package-private for the sake of testing
    Thing(String param1, int param2, Dependency1 param3, Dependency2 param4) {
        this.memberVar1 = param1;
        this.memberVar2 = param2;
        this.memberVar3 = param3;
        this.memberVar4 = param4;
    }


    //...rest of class...
}

在我看來,這是錯誤的,因為您正在編寫代碼來測試它而不是編寫正確的代碼。 我想其他兩個選項(我能想到的)是創建工廠或使用PowerMockito在適用的情況下注入新對象。 就個人而言,我會寫如下所示。

public class Thing {

    //public
    public Thing(String param1, int paramm2) {
        this.memberVar1 = param1;
        this.memberVar2 = param2;
        this.memberVar3 = new Dependency1();
        this.memberVar4 = new Dependency2();
    }

    //...rest of class...
}

實施此方法的最佳做法/正確方法是什么?

在任何已發布的內容中包含特定於測試的代碼通常是不好的形式(但有例外情況,所以不要過於仔細地閱讀)。 這有幾個原因。

1.外部人員可能會以一種你從未想過的方式使用測試構造函數,因為他們要么沒有讀取指示它用於測試的文檔,要么開發人員忘記記錄它。

假設您想要寫一些有用的Thing擴展,但是您在Thing的公共/受保護API中找不到任何您想要的東西。 然后你會發現這個包私有構造函數似乎允許你想要的東西,但后來才發現它破壞了你的代碼。 你仍然無法做你想做的事情你浪費時間探索沒有成功的部分API。 任何執行此操作的人都將對API持否定意見,並且不太可能將其推薦給其他任何人。

2.重構包名稱會破壞內容。

由於Java默認可見性的工作方式,此測試代碼對生產代碼中發生的重構不具有很強的適應性。 如果測試代碼位於同一個包中,則它只能調用該構造函數。 如果重命名包,則調用它的測試代碼將無法訪問它,從而導致編譯錯誤。 當然,對於開發代碼和測試的人來說,這是一個簡單的解決方案,但即使沒有添加這個小麻煩,重構也已經不好玩了。 如果一群人以前能夠成功地使用包私有的東西來滿足他們的需求,那么它就成了一個主要問題 - 現在他們所有的代碼都被打破了。

在某些情況下,很難編寫可以在測試和生產環境中運行的代碼(例如,僅在應用程序聯網時運行的功能)。 在這些情況下,依賴注入可能是你的朋友,但更簡單的測試總是最好的,如果可以避免更復雜的測試方案而不犧牲功能覆蓋或在API中添加鈎子,你從未打算讓其他開發人員看到。

我知道在StackExchange的其他地方對這個確切的話題進行了很好的討論,但我現在對谷歌的好運並不強烈。 我發現的副本不太有啟發性。

就個人而言,我已經在生產中看到了三種類型的測試代碼。

  1. 分支邏輯 - if (isTesting) foo() else bar();
  2. 測試方法 - Object foo(); 通過測試代碼調用但從未在生產中調用。
  3. 測試構造函數 - Foo(Object dependency1, Object dependency2)通過測試代碼調用,但從未在生產中調用。

前兩種類型在幾個方面都是有害的。

  • 可讀性受到影響,因為代碼更長,更復雜。 測試邏輯可以掩蓋業務邏輯。
  • 可維護性受到影響,因為代碼有更多的責任。 更新測試可能需要更改生產代碼。
  • 在最佳方案中,此測試代碼是生產中的死代碼。 在最壞的情況下,客戶端意外地在生產中執行此測試代碼,從而導致不可預測的結果。

第三種類型當然可以以上面列出的相同方式有害; 但是,我相信所謂的測試構造函數可以減輕或消除生產中其他類型測試代碼固有的問題。

  • 添加構造函數顯然會延長代碼(至少略微)但不需要增加復雜性:如果構造函數是鏈接的,就像在這里的示例中一樣,我們有一個實例化默認值而另一個只是分配字段。 這種分離可能比單獨的構造函數復雜得多。 如果我們遵循在構造函數中實現業務邏輯的普遍接受的做法,那么我們應該幾乎沒有模糊它的風險。
  • 再次假設我們的“測試”構造函數被鏈接到生產構造函數並且除了賦值之外沒有實現邏輯,代碼實際上沒有向類添加任何責任:它仍然實例化並分配依賴項,但是作為單獨的方法而不是一。 測試更改無法破壞我們的生產代碼。
  • 根據所概述的假設,我們的測試構造函數不是死代碼:它實際上是在生產中從客戶端調用的鏈接構造函數中調用的; 因此,執行第二個構造函數(帶有適當的參數)的客戶端可能期望與鏈式構造函數相同的合同。

在構造函數內部調用new通常是不好的,因為依賴注入通常是好的。 但是,在某些情況下,類的依賴項的合理默認實現是顯而易見的,並且在構造函數中實例化這些依賴項為客戶端提供了最簡單的可能接口。 在這些場景中,我發現鏈接第二個構造函數是合適的,它不會強制任何默認值,無論第二個構造函數是否有助於測試。

作為獎勵,鏈式構造函數實踐鼓勵編程到接口,因為第二個構造函數使您考慮除默認值之外的依賴項實現。

暫無
暫無

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

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