应用服务层:单元测试,集成测试还是两者兼而有之?

Jos*_*off 14 integration-testing unit-testing domain-driven-design

我的应用程序服务层中有很多方法都是这样做的:

public void Execute(PlaceOrderOnHoldCommand command)
{
    var order = _repository.Load(command.OrderId);
    order.PlaceOnHold();
    _repository.Save(order);
}
Run Code Online (Sandbox Code Playgroud)

目前,我有一堆像这样的单元测试:

[Test]
public void PlaceOrderOnHold_LoadsOrderFromRepository()
{
    var repository = new Mock<IOrderRepository>();
    const int orderId = 1;
    var order = new Mock<IOrder>();
    repository.Setup(r => r.Load(orderId)).Returns(order.Object);

    var command = new PlaceOrderOnHoldCommand(orderId);
    var service = new OrderService(repository.Object);
    service.Execute(command);

    repository.Verify(r => r.Load(It.Is<int>(x => x == orderId)), Times.Exactly(1));
}

[Test]
public void PlaceOrderOnHold_CallsPlaceOnHold()
{
            /* blah blah */
}

[Test]
public void PlaceOrderOnHold_SavesOrderToRepository()
{
            /* blah blah */
}
Run Code Online (Sandbox Code Playgroud)

这些单元测试是否增加了值得努力的价值似乎值得商榷.我很确定应用程序服务层应该进行集成测试.

应该将应用程序服务层测试到这种粒度级别,还是集成测试足够?

小智 12

尽管还有集成测试,我还是会编写单元测试.但是,通过消除模拟框架,编写自己的简单模拟,然后组合所有这些测试以检查模拟存储库中的顺序是否处于保持状态,我可能会使测试变得更加简单.

[Test]
public void PlaceOrderOnHold_LoadsOrderFromRepository()
{
    const int orderId = 1;
    var repository = new MyMockRepository();
    repository.save(new MyMockOrder(orderId));      
    var command = new PlaceOrderOnHoldCommand(orderId);
    var service = new OrderService(repository);
    service.Execute(command);
    Assert.IsTrue(repository.getOrder(orderId).isOnHold());
}
Run Code Online (Sandbox Code Playgroud)

确实没有必要检查以确保调用加载和/或保存.相反,我只是确保MyMockRepository返回更新顺序的唯一方法是调用load和save.

这种简化是我通常不使用模拟框架的原因之一.在我看来,如果你编写自己的模拟,你可以更好地控制你的测试,并且更容易编写它们.

  • 有趣的是鲍勃叔叔。不过看起来结束得很突然:) (2认同)

Mar*_*ijn 9

确切地说:这是值得商榷的!你正在权衡编写和维护测试的费用/努力与它带给你的价值是非常好的 - 而这正是你应该为你所编写的每一项考试做出的考虑.我经常看到为了测试而编写的测试,因此只在代码库中增加了镇流器.

作为指导我通常认为我想要对每个重要的成功场景/用例进行完整的集成测试.我将编写的其他测试是针对部分代码,这些代码可能会因未来的变化而中断,或者在过去已经破裂.这绝对不是所有代码.这就是您对系统和要求的判断和洞察力发挥作用的地方.

假设你有一个(集成)测试service.Execute(placeOrderOnHoldCommand),我不确定它是否会增加值来测试服务是否只从存储库加载一次订单.但它可能是!例如,当您的服务以前有一个令人讨厌的错误,它会在单个订单上打到存储库十次,从而导致性能问题(只是弥补).在那种情况下,我会将测试重命名为PlaceOrderOnHold_LoadsOrderFromRepositoryExactlyOnce().

因此,对于每一项测试,您必须自己决定......希望有所帮助.

笔记:

  1. 您展示的测试可以完全有效并且看起来很好.

  2. 您的测试序列方法似乎受到Execute(...)当前方法实现方式的启发.当您以这种方式构建测试时,可能是您将自己与特定实现联系在一起.这样,测试实际上可以使更改变得更加困难 - 确保您只测试类的重要外部行为.