Unit testing 如何测试聚合根的方法,这些方法只将其调用委托给AR';s实体?

Unit testing 如何测试聚合根的方法,这些方法只将其调用委托给AR';s实体?,unit-testing,oop,testing,tdd,domain-driven-design,Unit Testing,Oop,Testing,Tdd,Domain Driven Design,举一个具体的例子,我有PromoCodeAggregate Root,它由PromoCodeUsage实体组成,该实体仅由该AR控制,因此AR上的一些方法只是委托给该实体,如: public function useFor(Order $order): void { $this->promoCodeUsage->useFor($order); } 其中一些是部分授权的,如: public function applyFor(Order $order): void {

举一个具体的例子,我有
PromoCode
Aggregate Root,它由
PromoCodeUsage
实体组成,该实体仅由该AR控制,因此AR上的一些方法只是委托给该实体,如:

public function useFor(Order $order): void
{
    $this->promoCodeUsage->useFor($order);
}
其中一些是部分授权的,如:

public function applyFor(Order $order): void
{
    if (!$this->published) {
        throw new NotPublishedPromoCodeCanNotBeApplied();
    }

    $this->promoCodeUsage->applyFor($order);
}
我的测试套件完全涵盖了所有的
PromoCode
行为,包括
PromoCode用法
功能,因为在那个迭代中没有
PromoCode用法
,所有逻辑都混合在
PromoCode
中。然后,我将其中一些逻辑重构为
PromoCodeUsage
。这个针对
PromoCode
的测试套件有很多测试,我很高兴我也能拆分它(但即使在拆分实体之后,它也工作得很好)。因此,我创建了另一个测试套件(
PromoCodeUsageTest
),将部分测试从PromoCode中移出

但是
PromoCodeUsageTest
s正在通过
PromoCode
的行为测试
PromoCodeUsageTest
实体,与拆分前的原始测试相同。他们没有直接接触
promotCodeUsage
。现在我有了
PromoCodeTest
套件,其中包括: 和PromoteCodeUsageTest套件,包括:

但奇怪的是,1)在
PromoCodeTest
中,我省略了一些测试(在别处),2)在
PromoCodeUsageTest
中,我实际上没有触及
PromoCodeUsage
实体。3) 我使用Roy Osherove的模板进行测试命名,我不知道应该在测试名称中使用什么方法名称-从PromoCode还是PromoCodeUsage?在我的例子中,它们是相同的,但它们可能不同,而且这种想法很有味道

如果我重写
PromoCodeUsageTest
s来直接测试
PromoCodeUsage
实体,我最终会在
PromoCode
上找到一些未覆盖的方法(这些方法只是委托给
PromoCodeUsage
)。因此,我回到了通过
PromoCode
AR测试
PromoCode用法的方法

Bob叔叔(和其他人)说测试行为,而不是API是一种很好的做法。我的方法是否符合这一点


因为我在接近时感觉到一些气味,你呢?如何做得更好

考虑测试行为是正确的。我假设聚合的所有行为都是通过聚合根公开的,因此通过根进行测试是有意义的。我建议你命名你的测试来描述他们正在测试的行为。不要在测试名称中使用方法名称,因为这些名称可能会更改-这是将测试名称与生产代码的内部实现绑定在一起


如果一个测试类变得非常大,那么将它分成更小的类是有意义的——没有规则规定测试类和生产类之间必须有1:1的关系。然而,这可能意味着你的类,以及在这种情况下你的聚合,可能有太多的责任,可能会被分解成更小的部分

考虑测试行为是正确的。我假设聚合的所有行为都是通过聚合根公开的,因此通过根进行测试是有意义的。我建议你命名你的测试来描述他们正在测试的行为。不要在测试名称中使用方法名称,因为这些名称可能会更改-这是将测试名称与生产代码的内部实现绑定在一起


如果一个测试类变得非常大,那么将它分成更小的类是有意义的——没有规则规定测试类和生产类之间必须有1:1的关系。然而,这可能意味着你的类,以及在这种情况下你的聚合,可能有太多的责任,可能会被分解成更小的部分

我倾向于将聚合视为状态机,并相应地进行测试

重要的不是测试在哪个测试文件中,而是测试
PromoCode
聚合的所有可能结果状态,这取决于启动状态和您正在执行的PromoCode使用/应用程序的类型


当然,这可能需要深入研究聚合的内部,即依赖实体。例如,如果您更愿意将其断言着眼于
promotocodeusage
的所有测试放在不同的测试类中,那么很好,只要测试名称反映域而不是一些技术细节。

我倾向于将聚合视为状态机并相应地测试它们

重要的不是测试在哪个测试文件中,而是测试
PromoCode
聚合的所有可能结果状态,这取决于启动状态和您正在执行的PromoCode使用/应用程序的类型


当然,这可能需要深入研究聚合的内部,即依赖实体。例如,如果您更愿意将其断言着眼于
PromoCodeUsage
的所有测试放在不同的测试类中,那么很好,只要测试名称反映域而不是一些技术细节。

“在PromoCodeUsageTest中,我实际上没有触及PromoCodeUsage实体”-甚至在断言中也没有?@guillaume31 Nope,我是通过AggregateRoot断言的。而且,我并没有断言太多,我更没有断言要知道,当我已经测试了所有引发预期异常的案例时,可以在不引发异常/错误的情况下执行操作。因为我发现,我需要在我的实体上有getter才能执行这些断言,我认为这是不好的,不仅因为只有用于测试的方法,而且因为这样我会测试API而不是行为。“在PromoCodeUsageTest中,我实际上没有触及PromoCodeUsage实体”-甚至在断言中也没有?@guillaume31 Nope,我是通过AggregateRoot断言的。而且,我没有太多断言,我更没有断言要知道,当我已经测试了所有抛出期望值的情况时,可以在不抛出异常/错误的情况下执行操作