单元测试值对象与其依赖项隔离

时间:2014-03-08 01:10:20

标签: oop unit-testing dependency-injection tdd domain-driven-design

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构造函数中进行了硬编码,如果业务逻辑发生更改,我无法更改它们。

如何正确设计价值对象以测试和遵守注射剂和新品之间的分离?

注意:

  1. 这只是一个非常简单的例子。我的真实对象我有严肃的逻辑,也可能使用其他依赖。
  2. 我使用PHP示例仅用于说明。感谢其他语言的答案。

2 个答案:

答案 0 :(得分:0)

避免使用依赖于非值类型的值类型。还要避免执行验证并抛出异常的构造函数。在您的示例中,我有一个验证和创建数量的工厂类型。

答案 1 :(得分:0)

您的方案也可以应用于实体。在某些情况下,实体需要一些依赖关系才能执行某些行为。据我所知,最常用的机制是双重发送。

我将使用C#作为我的例子。

在你的情况下,你可以这样:

public void Validate(IQuantityValidator validator)

正如其他答案所指出的,值对象通常很简单,可以在构造函数中执行其不变检查。电子邮件价值对象就是一个很好的例子,因为电子邮件具有非常特殊的结构。

更复杂的东西可能是OrderLine我们需要确定,完全假设,是否是应税的:

public bool IsTaxable(ITaxableService service)

在你引用的文章中,我会声称'newable'与我们在特定实例中感兴趣的DI容器中的'瞬态'类型的生命周期有很大关系。但是,当我们需要注入特定值时,瞬态业务并没有真正帮助。对于每个都是新实例但具有非常不同状态的实体来说就是这种情况。存储库可以为对象提供水分,但它也可以使用工厂。

'true'依赖关系通常具有'singleton'生命周期。

因此,对于'newable'实例,如果您希望在构造时执行验证,则可以使用工厂,方法是让工厂使用Mark Seemann提到的注入验证器依赖关系在您的值对象上调用相关的验证方法。

这使您可以自由地进行单独测试,而无需耦合到构造函数中的特定实现。

对已经回答的问题略有不同的看法。希望它有所帮助:)