gpt4 book ai didi

oop - 与依赖项隔离的单元测试值对象

转载 作者:行者123 更新时间:2023-12-01 03:51:29 25 4
gpt4 key购买 nike

TL;DR
如何在不 stub 或注入(inject)它们的情况下独立于其依赖项来测试值对象?

在 Misko Hevery 的博文中 To “new” or not to “new”…他主张以下内容(引自博客文章):

  • An Injectable class can ask for other Injectables in its constructor.(Sometimes I refer to Injectables as Service Objects, but that term is overloaded.). Injectable can never ask for a non-Injectable (Newable) in its constructor.
  • Newables can ask for other Newables in their constructor, but not for Injectables (Sometimes I refer to Newables as Value Object, but again, the term is overloaded)


现在,如果我有 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 个验证器。通常我会通过构造函数注入(inject)这些验证器,这样我就可以 stub 他们在测试期间。

然而,根据 Misko 的说法,newable 不应该在其构造函数中要求注入(inject)。坦率地说 Quantity看起来像这样的对象 $quantity=new Quantity(1,$intValidator,$gtValidator);看起来真的很尴尬。

使用依赖注入(inject)框架来创建值对象更加尴尬。但是现在我的依赖项被硬编码在 Quantity 中。构造函数,如果业务逻辑发生变化,我无法更改它们。

您如何正确设计值(value)对象以测试和遵守注入(inject)剂和新剂之间的分离?

笔记:
  • 这只是一个非常简单的例子。我的真实对象在其中有严肃的逻辑,也可能使用其他依赖项。
  • 我使用了一个 PHP 示例来说明。其他语言的答案表示赞赏。
  • 最佳答案

    值对象应该只包含原始值(整数、字符串、 bool 标志、其他值对象等)。

    通常,最好让值对象本身 保护其不变量 .在您提供的 Quantity 示例中,它可以通过检查传入值轻松做到这一点,而无需依赖外部依赖项。但是,我意识到你写

    This is just a very very simplified example. My real object my have serious logic in it that may use other dependencies as well.



    因此,虽然我将基于 Quantity 示例概述一个解决方案,但请记住,它看起来过于复杂,因为这里的验证逻辑非常简单。

    既然你也写

    I used a PHP example just for illustration. Answers in other languages are appreciated.



    我将在 F# 中回答。

    如果您有外部验证依赖项,但仍希望将 Quantity 保留为值对象,则需要 解耦 来自值对象的验证逻辑。

    一种方法是定义一个验证接口(interface):
    type IQuantityValidator =
    abstract Validate : decimal -> unit

    在这种情况下,我设计了 Validate OP 示例中的方法,该方法在验证失败时引发异常。这意味着如果 Validate方法不会抛出异常,一切都很好。这就是该方法返回 unit 的原因。 .

    (如果我没有决定在 OP 上设计这个接口(interface),我会更喜欢使用 Specification pattern;如果是这样,我会改为将 Validate 方法声明为 decimal -> bool。)
    IQuantityValidator界面使您能够引入 Composite :
    type CompositeQuantityValidator(validators : IQuantityValidator list) =
    interface IQuantityValidator with
    member this.Validate value =
    validators
    |> List.iter (fun validator -> validator.Validate value)

    这个复合只是简单地遍历其他 IQuantityValidator实例并调用它们的 Validate方法。这使您能够组合任意复杂的验证器图。

    一个叶子验证器可以是:
    type IntegerValidator() =
    interface IQuantityValidator with
    member this.Validate value =
    if value % 1m <> 0m
    then
    raise(
    ArgumentOutOfRangeException(
    "value",
    "Quantity must be an integer."))

    另一种可能是:
    type GreaterThanValidator(boundary) =
    interface IQuantityValidator with
    member this.Validate value =
    if value <= boundary
    then
    raise(
    ArgumentOutOfRangeException(
    "value",
    "Quantity must be greater than zero."))

    注意 GreaterThanValidator类通过其构造函数获取依赖项。在这种情况下, boundary只是一个 decimal ,所以它是 Primitive Dependency ,但它也可能是多态依赖(A.K.A 服务)。

    您现在可以从这些构建 block 组成您自己的验证器:
    let myValidator =
    CompositeQuantityValidator([IntegerValidator(); GreaterThanValidator(0m)])

    当您调用 myValidator与例如 9m42m ,它返回没有错误,但如果你用例如调用它 9.8m , 0m-1m它抛出适当的异常。

    如果你想构建比 decimal 更复杂的东西,您可以引入工厂,并使用适当的验证器组成工厂。

    由于 Quantity 在这里非常简单,我们可以将其定义为 decimal 上的类型别名。 :
    type Quantity = decimal

    工厂可能如下所示:
    type QuantityFactory(validator : IQuantityValidator) =
    member this.Create value : Quantity =
    validator.Validate value
    value

    您现在可以撰写 QuantityFactory使用您选择的验证器的实例:
    let factory = QuantityFactory(myValidator)

    这将使您提供 decimal值作为输入,并获取(验证) Quantity值作为输出。

    这些调用成功:
    let x = factory.Create 9m
    let y = factory.Create 42m

    虽然这些抛出适当的异常:
    let a = factory.Create 9.8m
    let b = factory.Create 0m
    let c = factory.Create -1m

    现在,所有这些都是 非常复杂鉴于示例域的简单性质,但随着问题域变得越来越复杂, complex is better than complicated .

    关于oop - 与依赖项隔离的单元测试值对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22263646/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com