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

时间:2011-07-12 22:38:06

标签: unit-testing domain-driven-design integration-testing

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

public void Execute(PlaceOrderOnHoldCommand command)
{
    var order = _repository.Load(command.OrderId);
    order.PlaceOnHold();
    _repository.Save(order);
}

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

[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 */
}

这些单元测试是否值得付出努力似乎值得商榷。我很确定应用程序服务层应该进行集成测试。

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

3 个答案:

答案 0 :(得分: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());
}

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

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

答案 1 :(得分:9)

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

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

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

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

注意:

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

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

答案 2 :(得分:4)

我通常会编写主要方案的单一集成测试。主要场景我指的是所有被测试代码的成功路径。然后我编写了所有其他场景的单元测试,例如检查交换机中的所有案例,测试异常等等。

我认为同时拥有这两者是很重要的,是的,只能通过集成测试来测试它,但这会使您的测试长时间运行并且难以调试。平均而言,我认为每次集成测试我有10个单元测试。

我不打算用单行测试方法,除非在该行中发生类似逻辑的事情。

更新:为了说清楚,因为我正在进行测试驱动的开发我总是首先编写单元测试,并且通常在最后进行集成测试。