如何测试仅将调用委托给 AR 实体的聚合根方法?

How to test methods of Aggregate Root which just delegates their call to AR's entity?

举一个具体的例子,我有 PromoCode 聚合根,它由 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 行为,包括 PromoCodeUsage 功能,因为在那个迭代中没有 PromoCodeUsage 并且所有逻辑都混合在 PromoCode 中。然后我将其中的一些逻辑重构为 PromoCodeUsage。这个 PromoCode 的测试套件有很多测试,我很高兴我也可以拆分它(但即使在拆分实体后它也能很好地工作)。所以我创建了另一个测试套件 (PromoCodeUsageTest),其中我从 PromoCode 中移动了部分测试。

但是 PromoCodeUsageTest 正在通过 PromoCode 的行为测试 PromoCodeUsage 实体,就像在拆分之前的原始测试中一样。他们没有直接接触 PromoCodeUsage。现在我有 PromoCodeTest 套件: 和 PromoCodeUsageTest 套件:

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

如果我重写 PromoCodeUsageTests 以直接测试 PromoCodeUsage 实体,我最终会在 PromoCode 上得到一些未覆盖的方法(这些方法只是委托给 PromoCodeUsage)。所以这让我回到了通过 PromoCode AR.

测试 PromoCodeUsage 的方法

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

因为我觉得我的方法有点味道,你呢?如何做得更好?

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

如果测试 class 变得非常大,将其分解成更小的 class 是有意义的 - 没有规定测试和生产之间必须有 1:1 关系classes。但是,这可能表明您的 class,在这种情况下,您的聚合可能有太多责任,可能被分解成更小的部分。

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

重要的不是测试在哪个测试文件中,而是根据起始状态和促销代码的类型测试 PromoCode 聚合的所有可能结果状态 usage/application正在做。

当然,这可能需要深入了解聚合的内部,了解相关实体。例如,如果您更愿意放入不同的测试 class 所有其断言查看 PromoCodeUsage 的测试,那么很好,只要测试名称反映域而不是一些技术细节。