我发现生成器设计模式偶尔在代码中有用,但在测试中经常有用。 本文简要概述了该模式,然后介绍了在测试中使用该模式的一个有效示例。 请参阅github中的代码。

生成器模式的背景

根据GoF的书 ,构建器设计模式用于“将复杂对象的构造与其表示分离,以便同一构造过程可以创建不同的表示”。 像大多数GoF书中一样,这是一个准确而乏味的描述。

乔什·布洛赫(Josh Bloch)在他的《 有效的Java》一书中,为建造者提出了一种更有趣的用法。 他的方法试图解决的问题是,当一个类具有“不止几个”参数时,这些参数通常是通过构造函数设置的,其中许多参数可能是可选的。 典型的解决方案是

  • 伸缩构造函数模式,在该模式中,您将为构造函数仅提供必需的参数,并为其他构造函数提供可选参数的变体,最终形成具有所有可选参数的构造函数。
    这可以工作,但会导致一个相当混乱的解决方案,该解决方案可能包含大量构造函数以覆盖所有排列
  • 一个简单的构造函数(例如,仅用于必需的参数),由setter方法支持可选参数(JavaBeans方法)。 但是,这可能会使对象在构造过程中处于不一致状态,并且由于无法将字段定为final ,因此当然会阻止不变性。
  • 使用一个生成器。 这是Bloch建议的方法。 客户端创建一个生成器(通常使用无参数的构造器),然后在最终调用一个build方法之前,调用类似setter的方法来获取感兴趣的值(其余的假定为默认值)。

几年前,我参加了一次演讲 ,其中Ted Young讨论了通过将构建器模式用于测试对象的构建来使构建器模式更进一步,下面讨论的是这种方法。 [更新:请在此处查看Ted对这篇文章的回复]

使用Builder模式构造测试装置

使用Builder可以更轻松,更清晰地创建测试装置。

我通常使用此Builder方法进行测试的对象类型是域模型对象,例如Account,User,Widget或其他对象。 我支持使此类对象不变 。
例如:

public final class Account {private final Integer id;private final String name;private final AccountType type;private final BigDecimal balance;private final DateTime openDate;private final Status status;public Account(Integer id, String name, AccountType type,BigDecimal balance, DateTime openDate, Status status) {this.id = id;this.name = name;this.type = type;this.balance = balance;this.openDate = openDate;this.status = status;}public Integer getId() {return id;}    //other getters, toString(), equals() and hashCode() omitted for brevity//no setters
}

使用此类,您经常会遇到Bloch讨论的问题。 在此示例中,我们有一个强制您设置所有值的构造函数,但是我们也可以有很多变体,其中一些值可以省略以使用默认值。 因此,为测试创建此类的实例可能会有些痛苦,如果它具有比此简单示例更多的字段,则更加痛苦。 您甚至不得不为可能不需要测试的字段提供值。 这也使得很难知道哪些值实际上是测试所需要的,哪些值纯粹是为了编译。

建设者可以提供帮助。

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);}
}

现在,您可以创建一个Account对象以更轻松地进行测试。

关于使用Builder进行测试的注意事项

  • 默认值

构建器中使用的缺省值是避免出现异常的便利。 如果您的测试需要特定的测试值,则最好明确设置它们,而不要依赖任何默认值。 它使您的测试意图更加清晰,并且在您需要更改默认值(例如由于业务需求变化)而使无意中止测试的风险最小化的情况。

  • 非最终领域

域模型类本身是不可变的,因此具有最终字段。 根据设计,生成器中的所有字段都是非最终的。 因此,构建器不是线程安全的。 因此,请勿重复使用Builders; 而是为每个测试创建一个新实例。

  • 方法顺序不大

在大多数情况下,在构建器上调用方法的顺序应该不重要,并且在调用build()之前不会构造该对象。 这使构建器更易于使用,并避免了意外的意外。
此经验法则有明显且可以接受的例外。
例如打电话

Account account = new AccountBuilder().withType(AccountType.SAVING).withType(AccountType.CHECKING).build();

很傻,但被允许。 它只会为您提供类型检查的权限。
很好,但是请尽量避免引起混乱的细微原因,例如,如果您的集合中可以添加某些内容,或者替换了整个集合(因此请删除以前的添加内容)。

使用Builder进行测试的优势

  • 易于阅读

以下声明不是特别清楚:

Account account = new Account(1, "test", 10, ...);

该声明更加清晰:

Account account = new AccountBuilder().withId(1).withName("test").withBalance(10).build();

正如Bloch所说,“ Builder模式模拟命名的可选参数”。

  • 仅指定与您的测试实际相关的值

如果您的测试仅涉及帐户余额和状态:

Account account = new AccountBuilder().withBalance(new BigDecimal(-100)).withStatus(Status.OVERDRAWN).build();

与必须在Accounts构造函数中指定每个值相反。

  • 创建无效对象的能力

域模型类的构造函数可能会(希望!)强迫您创建有效的对象。 在测试中,您可能要故意创建无效的对象以进行测试。

进一步的增强

便利方法

您可以为测试中使用的常见方案添加便捷方法。
例如

public AccountBuilder withNegativeBalance() {this.balance = new BigDecimal(-100);return this;}

灯具类

除了使用Builder类之外,我还发现拥有一个关联的Fixtures类很有用,该类提供用于测试的预构建实例。 这些可以利用Builder对象进行构造(尽管也没有什么可以阻止您使用原始构造函数)。
例如

public class AccountFixtures {//a shortcut to creating a basic Account objectpublic final Account ACCOUNT = new AccountBuilder().build();public final Account OVERDRAWN_CHECKING_ACCOUNT = new AccountBuilder().withType(AccountType.CHECKING).withNegativeBalance().build();public final Account CLOSED_SAVING_ACCOUNT = new AccountBuilder().withType(AccountType.SAVING).withZeroBalance().withStatus(Status.CLOSED).build();
}
参考: 构建器模式:适用于代码,非常适合我们的JCG合作伙伴 Shaun Abram在Shaun Abram博客博客中进行的测试。

翻译自: https://www.javacodegeeks.com/2013/06/builder-pattern-good-for-code-great-for-tests.html

构建器模式:适用于代码,适用于测试相关推荐

  1. fusion构建器代码语法_构建器模式:适用于代码,适用于测试

    fusion构建器代码语法 我发现构建器设计模式偶尔在代码中有用,但在测试中经常有用. 本文简要概述了该模式,然后介绍了在测试中使用该模式的一个有效示例. 请参阅github中的代码. 生成器模式的背 ...

  2. 构建器模式_我喜欢构建器模式的三个原因

    构建器模式 有三种方法可以用Java编程语言创建新对象: 伸缩构造函数(反)模式 Javabeans模式 建造者模式 与其他两种方法相比,我更喜欢使用构建器模式. 为什么? Joshua Bloch描 ...

  3. 我喜欢构建器模式的三个原因

    有三种方法可以用Java编程语言创建新对象: 伸缩构造函数(反)模式 Javabeans模式 建造者模式 与其他两种方法相比,我更喜欢使用构建器模式. 为什么? Joshua Bloch描述了构建器模 ...

  4. 使用构建器模式来帮助您的单元测试

    在这篇文章中,您将看到一个使用构建器模式来帮助对具有多个依赖项的服务进行单元测试的示例 您是否曾经需要对具有六个(或更多)依赖项的服务或外观进行单元测试?我会说--依赖注入很棒,但是测试具有太多依赖项 ...

  5. 设计模式心得1(工厂模式+单例模式+构建器模式+原型模式+适配器模式)

    设计模式分类 大致按照模式的应用目标分类,设计模式可以分为创建型模式.结构型模式和行为型模式. 创建型模式,是对对象创建过程的各种问题和解决方案的总结,包括各种工厂模式(Factory.Abstrac ...

  6. 11Builder(构建器)模式

    技术交流QQ群:1027579432,欢迎你的加入! 1.Builder(构建器)模式动机 在软件系统中,有时候面临着一个复杂对象的创建工作,其通常由各个部分的子对象用一定的算法构成.由于需求的变化, ...

  7. java 构建者模式_Java方法中的参数太多,第3部分:构建器模式

    java 构建者模式 在我的前两篇文章中,我研究了如何通过自定义类型和参数对象减少构造函数或方法调用所需的参数数量. 在本文中,我将讨论如何使用构建器模式来减少构造器所需的参数数量,并讨论该模式如何甚 ...

  8. Java方法中的参数太多,第3部分:构建器模式

    在我的前两篇文章中,我研究了如何通过自定义类型和参数对象减少构造函数或方法调用所需的参数数量. 在本文中,我将讨论如何使用构建器模式来减少构造器所需的参数数量,并讨论该模式如何甚至可以帮助采用过多参数 ...

  9. Effective Java(一)———— 代替构造器和Setter的构建器模式

    引言 Java语言中的一部经典著作<Effective Java>,里面涵盖了78条我们应该熟练的Java编程技巧. 本篇博客是该书学习的系列笔记第一篇.本系列博客不会与书中的78条建议完 ...

最新文章

  1. 开源项目在GitHub上贡献33.5W个Star!腾讯的十年“云”答卷,请收好!
  2. 2011最有用最潮的jQuery特效教程,前端们抱走吧~
  3. sql查询,nolock写还是不写,这是一个问题
  4. python封装函数、实现将任意的对象序列化到磁盘上_Python系列之lambda、函数、序列化...
  5. ZOJ2091(贪心)
  6. python异步和多线程_Python 异步 IO(asyncio)、多进程、多线程性能对比
  7. 【图像处理】——灰度变换心得(cv2.normalize规范化值0-255,cv2.convertScaleAbs(new_img)转为8位图)
  8. [转]用了docker是否还有必要使用openstack?
  9. 想要玩转Mac?试试这几款神器吧!
  10. python的类变量和成员变量用法_python中类变量和成员变量、局部变量总结
  11. 给大家分享一个QQ资料查询接口(等级,活跃,年龄,性别,身份卡)
  12. win10 双屏显示 鼠标可以从主屏幕左侧滑入右侧竖屏
  13. Atlas for CDH6.3.2 高可用安装与配置
  14. 三星笔记本 win10+ubuntu18.04.3双系统 BIOS设置
  15. 观测云高分通过等保三级认证,信息安全体系建设领先行业
  16. 穷爸爸,富爸爸学习笔记
  17. Tableau收购慕尼黑工业大学(TUM)开发的高性能数据库系统HyPer
  18. 关于Datatable删除行和删除列
  19. Python编程之斐波那契数列
  20. 苹果ll是什么版本_新的iPad Pro和旧版本有什么区别?苹果背后的策略是什么?...

热门文章

  1. python中的数组按顺序切片_python切片(获取一个子列表(数组))详解
  2. 磁珠 符号_贴片磁珠功能_贴片磁珠应用
  3. mysql的on和in用法_数据库中in、on、with的用法及示例。
  4. 通过OAuth 2.0和Okta使用安全的服务器到服务器通信构建Spring Boot应用
  5. groovy grails_在Grails战争中添加一个“精简”的Groovy Web控制台
  6. apache pulsar_Apache Pulsar:分布式Pub-Sub消息系统
  7. 移动端apm关键指标_3个经常被忽视的APM关键功能
  8. java ee的小程序_Java EE应用程序的单片到微服务重构
  9. vue 脚手架测试环境_关于单元测试脚手架的几点思考
  10. Java正成为COBOL的一部分-它将成为COBOL的一部分吗?