TL; DR
如何在不对其进行存根或注入的情况下,独立于其依赖项来测试值对象?
在Misko Hevery的博文To “new” or not to “new”…中,他主张以下内容(引自博客文章):
- Injectable类可以在其构造函数中请求其他Injectable。(有时我将Injectables称为Service Objects,但是 那个词超载了。) Injectable永远不会在其构造函数中请求非Injectable(Newable)。
- Newables可以在构造函数中请求其他Newables,但不能用于Injectables(有时我将Newables称为Value Object,但是 再次,该术语超载)
现在,如果我有一个Quantity
值对象:
class Quantity{
$quantity=0;
public function __construct($quantity){
$intValidator = new Zend_Validate_Int();
if(!$intValidator->isValid($quantity)){
throw new Exception("Quantity must be an integer.");
}
$gtValidator = new Zend_Validate_GreaterThan(0);
if(!$gtvalidator->isValid($quantity)){
throw new Exception("Quantity must be greater than zero.");
}
$this->quantity=$quantity;
}
}
我的Quantity
值对象依赖于至少2个验证器来正确构造。通常我会通过构造函数注入那些验证器,这样我就可以在测试期间存根它们。
然而,根据Misko的说法,新手不应该在其构造函数中要求注射剂。坦率地说,Quantity
对象看起来像这样
$quantity=new Quantity(1,$intValidator,$gtValidator);
看起来很尴尬。
使用依赖注入框架来创建值对象更加尴尬。但是现在我的依赖项在Quantity
构造函数中进行了硬编码,如果业务逻辑发生更改,我无法更改它们。
如何正确设计价值对象以测试和遵守注射剂和新品之间的分离?
注意:
答案 0 :(得分:0)
避免使用依赖于非值类型的值类型。还要避免执行验证并抛出异常的构造函数。在您的示例中,我有一个验证和创建数量的工厂类型。
答案 1 :(得分:0)
您的方案也可以应用于实体。在某些情况下,实体需要一些依赖关系才能执行某些行为。据我所知,最常用的机制是双重发送。
我将使用C#作为我的例子。
在你的情况下,你可以这样:
public void Validate(IQuantityValidator validator)
正如其他答案所指出的,值对象通常很简单,可以在构造函数中执行其不变检查。电子邮件价值对象就是一个很好的例子,因为电子邮件具有非常特殊的结构。
更复杂的东西可能是OrderLine
我们需要确定,完全假设,是否是应税的:
public bool IsTaxable(ITaxableService service)
在你引用的文章中,我会声称'newable'与我们在特定实例中感兴趣的DI容器中的'瞬态'类型的生命周期有很大关系。但是,当我们需要注入特定值时,瞬态业务并没有真正帮助。对于每个都是新实例但具有非常不同状态的实体来说就是这种情况。存储库可以为对象提供水分,但它也可以使用工厂。
'true'依赖关系通常具有'singleton'生命周期。
因此,对于'newable'实例,如果您希望在构造时执行验证,则可以使用工厂,方法是让工厂使用Mark Seemann提到的注入验证器依赖关系在您的值对象上调用相关的验证方法。
这使您可以自由地进行单独测试,而无需耦合到构造函数中的特定实现。
对已经回答的问题略有不同的看法。希望它有所帮助:)