应用服务层:单元测试,集成测试还是两者?
我有一大堆的在我的应用服务层的方法正在做这样的事情:应用服务层:单元测试,集成测试还是两者?
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 */
}
这些单元测试是否增加值得付出努力的价值似乎是有争议的。不过,我确信应用服务层应该进行集成测试。
应该测试应用程序服务层到这个粒度级别还是集成测试足够?
确切的说:这是值得商榷的!确实很重要的一点是,你正在考虑编写和维护测试的费用/努力与它将带给你的价值 - 这正是你为每个测试写的考虑因素。通常我会看到为了测试而编写的测试,因此仅向代码库添加了镇流器。
作为一个指导方针,我通常认为我需要对每个重要的成功案例/用例进行完整的集成测试。我会写的其他测试是针对的部分代码可能会与未来的更改相冲突,或者在过去的中破解。这绝对不是全部代码。这就是您对系统和要求的判断和洞察力发挥作用的地方。
假设您有一个service.Execute(placeOrderOnHoldCommand)
的(集成)测试,我不确定它是否增加了测试值是否会增加测试服务器是否从存储库中仅加载一次订单的值。但它可能是!例如,如果您的服务之前有一个令人讨厌的错误,会导致存储库十次出现单个订单,从而导致性能问题(只是补充)。在那种情况下,我会将测试重命名为PlaceOrderOnHold_LoadsOrderFromRepositoryExactlyOnce()
。
因此,对于每一个测试,你必须为自己决定...希望有所帮助。
注:
测试显示您可以完全有效的,并期待写得很好。
您的测试序列方法似乎受到当前实施方法
Execute(...)
的启发。当你用这种方式构建你的测试时,可能是你正在将自己绑定到特定的实现。通过这种方式,测试实际上可以让测试变得更加困难 - 确保您只测试班级的重要外部行为。
我通常会编写主要场景的单个集成测试。通过主要场景,我的意思是所有正在测试的代码的成功路径。然后我编写所有其他场景的单元测试,例如检查交换机中的所有情况,测试异常等等。
我认为这两者都是非常重要的,而且可以仅用集成测试来测试它,但这会让您的测试长时间运行并且难以调试。平均而言,我认为每个集成测试有10个单元测试。
我不打扰用单线测试方法,除非在那一行发生类似逻辑的事情。
更新:只是为了说清楚,因为我在做测试驱动开发,我总是先写单元测试,最后通常会进行集成测试。
尽管还有一个集成测试,我还是写一个单元测试。但是,通过消除模拟框架,编写我自己的简单模拟,然后结合所有这些测试来检查模拟存储库中的顺序是否处于保持状态,我可能会使测试更简单。
[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。
这种简化是我通常不使用模拟框架的原因之一。在我看来,如果你写自己的模拟,你对测试有更好的控制,并且写出它们更容易。
有趣的拿鲍勃叔叔。看起来它突然结束了,虽然:) –
但也有可能测试'Order.PlaceOnHold()'。因此,如果现在您决定使用ID 1的订单不能被搁置,您有2个测试来修复 – driushkin
@unclebob谢谢!我将考虑这个问题。 @driushkin确实有'Order.PlaceOnHold()'的测试。但是,是否可以暂停订单完全是实施真正订单类的关注 - 模拟不会对服务方法中发生的情况产生任何影响。 –
这些都是真的你提出的好点。我想我会先编写集成测试*,然后查看单元测试可能带来的附加值(例如,在提供错误订单ID时抛出InvalidOperationException)。而且你也提出了一个关于测试实现的好点 - 关键是输入和输出,而不是实现。 –