派生、组合、构造函数、接口和 TDD
Derivation, composition, constructors, interfaces and TDD
在开发 TDD 时,您的对象 'grow' 随着代码的发展。首先它们只包含一些功能,然后您添加新的功能。你基本上可以用组合 and/or 继承来做到这一点。
同时,您几乎总是使用接口(用于测试和生产代码)来确保低耦合、依赖倒置、依赖注入和模拟。
你如何管理这个?
我的意思是,例如:您创建了一个 class,现在它必须包含一个新属性(例如,一个人现在可以有一个电子邮件)。怎么办?
- 修改class,添加新属性: 然后你必须添加新的构造函数。但是,如果您修改现有的构造函数,那么您必须重构所有涉及的测试,而不仅仅是生产代码。您可以尝试在添加新属性时保留所有构造函数,但最后大部分都变得无用、过时(仅由于测试而存在),具有复杂的逻辑 'constructor calling another constructors' (或使用某种 'factory')
- 导出 class: 这更符合 OCP。但是如果你做一个真正的 TDD,用小的步骤,如果你总是从一个非常简单的 class 开始派生,你将以一个非常复杂且不知何故不必要的继承结束。此外,假设您始终只能测试 PUBLIC 暴露的成员。因此,同样,随着基础 classes 受到保护,必须重构测试以访问 public 那些。
- Compose: Composing 对构造函数没有问题,但并不总是一个好的解决方案。至于我们现在的例子。
在某种程度上,接口也会发生类似的事情。
因此,随着代码的增长,构造函数和接口必须进化。我心中有三戒:
- 你应该避免重构测试。
- 生产代码不得保留'code only intended for testing'(即构造函数)
- ISP 声明使用特定接口。随着代码的增长,其中许多变得不必要,因为它们仅用于测试(即模拟)class在许多情况下,应该保护或私有。
所以,正如我所说...我如何在开发 TDD 时管理所有这些?我知道这是一个需要很长答案的问题,所以,如果您可以参考一些好的文章或资源
更改测试即可
您所描述的(此人现在有电子邮件)是规格的变化。单元测试应该成为您的规范,因此在引入新数据时必须更改它们是很正常的。
这种测试脆弱性是对质量的权衡,是一种不可避免的罪恶。
不要过度设计
尝试应用 OCP、继承和组合来添加一个字段,只会让事情变得更复杂。正如您所指出的,它只是为了测试而导致生产代码异常,而且您还必须修改测试。
如果您要在现有行为系列中添加新的行为,OCP 可能会很好,但这里不是这种情况。
使更改更易于管理
有策略可以让你的测试更能抵抗小的变化,但它们主要是在测试级别实现的,而不是在生产代码。此外,如果您经常在测试中寻找重复,例如在 TDD 周期的重构步骤中,那么很容易发现这种情况的机会。
其中之一是将您的 "system under test" 创建封装在 Factory Method 中,并在每个测试中调用它而不是普通构造函数。使用默认值使其大部分参数可选。这将允许您在一个地方进行更改(例如添加电子邮件字段),所有测试都将从中受益。这 并不能避免您修改测试套件 ,但您只需在一个地方完成。
在开发 TDD 时,您的对象 'grow' 随着代码的发展。首先它们只包含一些功能,然后您添加新的功能。你基本上可以用组合 and/or 继承来做到这一点。
同时,您几乎总是使用接口(用于测试和生产代码)来确保低耦合、依赖倒置、依赖注入和模拟。
你如何管理这个?
我的意思是,例如:您创建了一个 class,现在它必须包含一个新属性(例如,一个人现在可以有一个电子邮件)。怎么办?
- 修改class,添加新属性: 然后你必须添加新的构造函数。但是,如果您修改现有的构造函数,那么您必须重构所有涉及的测试,而不仅仅是生产代码。您可以尝试在添加新属性时保留所有构造函数,但最后大部分都变得无用、过时(仅由于测试而存在),具有复杂的逻辑 'constructor calling another constructors' (或使用某种 'factory')
- 导出 class: 这更符合 OCP。但是如果你做一个真正的 TDD,用小的步骤,如果你总是从一个非常简单的 class 开始派生,你将以一个非常复杂且不知何故不必要的继承结束。此外,假设您始终只能测试 PUBLIC 暴露的成员。因此,同样,随着基础 classes 受到保护,必须重构测试以访问 public 那些。
- Compose: Composing 对构造函数没有问题,但并不总是一个好的解决方案。至于我们现在的例子。
在某种程度上,接口也会发生类似的事情。
因此,随着代码的增长,构造函数和接口必须进化。我心中有三戒:
- 你应该避免重构测试。
- 生产代码不得保留'code only intended for testing'(即构造函数)
- ISP 声明使用特定接口。随着代码的增长,其中许多变得不必要,因为它们仅用于测试(即模拟)class在许多情况下,应该保护或私有。
所以,正如我所说...我如何在开发 TDD 时管理所有这些?我知道这是一个需要很长答案的问题,所以,如果您可以参考一些好的文章或资源
更改测试即可
您所描述的(此人现在有电子邮件)是规格的变化。单元测试应该成为您的规范,因此在引入新数据时必须更改它们是很正常的。
这种测试脆弱性是对质量的权衡,是一种不可避免的罪恶。
不要过度设计
尝试应用 OCP、继承和组合来添加一个字段,只会让事情变得更复杂。正如您所指出的,它只是为了测试而导致生产代码异常,而且您还必须修改测试。
如果您要在现有行为系列中添加新的行为,OCP 可能会很好,但这里不是这种情况。
使更改更易于管理
有策略可以让你的测试更能抵抗小的变化,但它们主要是在测试级别实现的,而不是在生产代码。此外,如果您经常在测试中寻找重复,例如在 TDD 周期的重构步骤中,那么很容易发现这种情况的机会。
其中之一是将您的 "system under test" 创建封装在 Factory Method 中,并在每个测试中调用它而不是普通构造函数。使用默认值使其大部分参数可选。这将允许您在一个地方进行更改(例如添加电子邮件字段),所有测试都将从中受益。这 并不能避免您修改测试套件 ,但您只需在一个地方完成。