构建器模式:适用于代码,适用于测试
我發(fā)現(xiàn)生成器設(shè)計模式偶爾在代碼中有用,但在測試中經(jīng)常有用。 本文簡要概述了該模式,然后介紹了在測試中使用該模式的一個有效示例。 請參閱github中的代碼。
生成器模式的背景
根據(jù)GoF的書 ,構(gòu)建器設(shè)計模式用于“將復(fù)雜對象的構(gòu)造與其表示分離,以便同一構(gòu)造過程可以創(chuàng)建不同的表示”。 像大多數(shù)GoF書中一樣,這是一個準(zhǔn)確而乏味的描述。
喬什·布洛赫(Josh Bloch)在他的《 有效的Java》一書中,為建造者提出了一種更有趣的用法。 他的方法試圖解決的問題是,當(dāng)一個類具有“不止幾個”參數(shù)時,這些參數(shù)通常是通過構(gòu)造函數(shù)設(shè)置的,其中許多參數(shù)可能是可選的。 典型的解決方案是
- 伸縮構(gòu)造函數(shù)模式,在該模式中,您將為構(gòu)造函數(shù)僅提供必需的參數(shù),并為其他構(gòu)造函數(shù)提供可選參數(shù)的變體,最終形成具有所有可選參數(shù)的構(gòu)造函數(shù)。
這可以工作,但會導(dǎo)致一個相當(dāng)混亂的解決方案,該解決方案可能包含大量構(gòu)造函數(shù)以覆蓋所有排列 - 一個簡單的構(gòu)造函數(shù)(例如,僅用于必需的參數(shù)),由setter方法支持可選參數(shù)(JavaBeans方法)。 但是,這可能會使對象在構(gòu)造過程中處于不一致狀態(tài),并且由于無法將字段定為final ,因此當(dāng)然會阻止不變性。
- 使用一個生成器。 這是Bloch建議的方法。 客戶端創(chuàng)建一個生成器(通常使用無參數(shù)的構(gòu)造器),然后在最終調(diào)用一個build方法之前,調(diào)用類似setter的方法來獲取感興趣的值(其余的假定為默認(rèn)值)。
幾年前,我參加了一次演講 ,其中Ted Young討論了通過將構(gòu)建器模式用于測試對象的構(gòu)建來使構(gòu)建器模式更進(jìn)一步,下面討論的是這種方法。 [更新:請在此處查看Ted對這篇文章的回復(fù)]
使用Builder模式構(gòu)造測試裝置
使用Builder可以更輕松,更清晰地創(chuàng)建測試裝置。
我通常使用此Builder方法進(jìn)行測試的對象類型是域模型對象,例如Account,User,Widget或其他對象。 我支持使此類對象不變 。
例如:
使用此類,您經(jīng)常會遇到Bloch討論的問題。 在此示例中,我們有一個強制您設(shè)置所有值的構(gòu)造函數(shù),但是我們也可以有很多變體,其中一些值可以省略以使用默認(rèn)值。 因此,為測試創(chuàng)建此類的實例可能會有些痛苦,如果它具有比此簡單示例更多的字段,則更加痛苦。 您甚至不得不為可能不需要測試的字段提供值。 這也使得很難知道哪些值實際上是測試所需要的,哪些值純粹是為了編譯。
建設(shè)者可以提供幫助。
例
public class AccountBuilder {//account fields with default valuesInteger id = 1;String name = "default account name";AccountType type = AccountType.CHECKING;BigDecimal balance = new BigDecimal(0);DateTime openDate = new DateTime(2013, 01, 01, 0, 0, 0);Status status = Status.ACTIVE;public AccountBuilder() {}public AccountBuilder withId(Integer id) {this.id = id;return this;}public AccountBuilder withName(String name) {this.name = name;return this;}public AccountBuilder withType(AccountType type) {this.type = type;return this;}public AccountBuilder withBalance(BigDecimal balance) {this.balance = balance;return this;}public AccountBuilder withOpenDate(DateTime openDate) {this.openDate = openDate;return this;}public AccountBuilder withStatus(Status status) {this.status = status;return this;}public Account build() {return new Account(id, name, type, balance, openDate, status);} }現(xiàn)在,您可以創(chuàng)建一個Account對象以更輕松地進(jìn)行測試。
關(guān)于使用Builder進(jìn)行測試的注意事項
- 默認(rèn)值
構(gòu)建器中使用的缺省值是避免出現(xiàn)異常的便利。 如果您的測試需要特定的測試值,則最好明確設(shè)置它們,而不要依賴任何默認(rèn)值。 它使您的測試意圖更加清晰,并且在您需要更改默認(rèn)值(例如由于業(yè)務(wù)需求變化)而使無意中止測試的風(fēng)險最小化的情況。
- 非最終領(lǐng)域
域模型類本身是不可變的,因此具有最終字段。 根據(jù)設(shè)計,生成器中的所有字段都是非最終的。 因此,構(gòu)建器不是線程安全的。 因此,請勿重復(fù)使用Builders; 而是為每個測試創(chuàng)建一個新實例。
- 方法順序不大
在大多數(shù)情況下,在構(gòu)建器上調(diào)用方法的順序應(yīng)該不重要,并且在調(diào)用build()之前不會構(gòu)造該對象。 這使構(gòu)建器更易于使用,并避免了意外的意外。
此經(jīng)驗法則有明顯且可以接受的例外。
例如打電話
很傻,但被允許。 它只會為您提供類型檢查的權(quán)限。
很好,但是請盡量避免引起混亂的細(xì)微原因,例如,如果您的集合中可以添加某些內(nèi)容,或者替換了整個集合(因此請刪除以前的添加內(nèi)容)。
使用Builder進(jìn)行測試的優(yōu)勢
- 易于閱讀
以下聲明不是特別清楚:
Account account = new Account(1, "test", 10, ...);該聲明更加清晰:
Account account = new AccountBuilder().withId(1).withName("test").withBalance(10).build();正如Bloch所說,“ Builder模式模擬命名的可選參數(shù)”。
- 僅指定與您的測試實際相關(guān)的值
如果您的測試僅涉及帳戶余額和狀態(tài):
Account account = new AccountBuilder().withBalance(new BigDecimal(-100)).withStatus(Status.OVERDRAWN).build();與必須在Accounts構(gòu)造函數(shù)中指定每個值相反。
- 創(chuàng)建無效對象的能力
域模型類的構(gòu)造函數(shù)可能會(希望!)強迫您創(chuàng)建有效的對象。 在測試中,您可能要故意創(chuàng)建無效的對象以進(jìn)行測試。
進(jìn)一步的增強
便利方法
您可以為測試中使用的常見方案添加便捷方法。
例如
燈具類
除了使用Builder類之外,我還發(fā)現(xiàn)擁有一個關(guān)聯(lián)的Fixtures類很有用,該類提供用于測試的預(yù)構(gòu)建實例。 這些可以利用Builder對象進(jìn)行構(gòu)造(盡管也沒有什么可以阻止您使用原始構(gòu)造函數(shù))。
例如
翻譯自: https://www.javacodegeeks.com/2013/06/builder-pattern-good-for-code-great-for-tests.html
總結(jié)
以上是生活随笔為你收集整理的构建器模式:适用于代码,适用于测试的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国有电脑品牌(中国电脑品牌有哪些牌子)
- 下一篇: 分布式系统开发注意事项