开发人员2:我们还需要考虑这个问题的另一个方面:我们可能有一组测试来测试领域对象,并且所有这些测试都可能通过。我们还可能有一组测试来验证ORM层是否能够成功地保存和获取对象。但是,我们还必须测试领域对象在ORM层上运行时是否正确。领域对象有可能执行正确的业务逻辑,但无法正确的持久化其状态,这可能是因为ORM处理特定数据类型的方式存在问题。
有关这里讨论的两种测试方法的更多信息,请参阅Martin Fowler的文章“Mocks Aren't Stubs”和Steve Freeman、Nat Pryce和Joshua Kerievsky编写的“Point/Counterpoint”。
备注:解决方案中包含的测试是使用xUnit.net编写的。下面的代码示例展示了使用上面讨论的行为方法编写的两个测试示例。
Markus(软件开发人员)发言:这些是我们刚开始时使用的测试,但是我们随后用基于状态的测试替换了它们。 public SeatsAvailability given_available_seats() { var sut = new SeatsAvailability(SeatTypeId); sut.AddSeats(10); return sut; } [TestMethod] public void when_reserving_less_seats_than_total_then_succeeds() { var sut = this.given_available_seats(); sut.MakeReservation(Guid.NewGuid(), 4); } [TestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] public void when_reserving_more_seats_than_total_then_fails() { var sut = this.given_available_seats(); sut.MakeReservation(Guid.NewGuid(), 11); }
这两个测试共同验证了可用座位(SeatsAvailability)聚合的行为。在第一个测试中,预期的行为是MakeReservation方法成功,并且不会抛出异常。在第二个测试中,MakeReservation方法的预期行为是抛出异常,因为没有足够的空闲座位来完成预订。
如果没有聚合引发事件,则很难以任何其他方式测试行为。例如,检查是否进行了正确的调用以将聚合持久化到数据存储里,如果您试图用这个来测试行为,那么测试就会和数据存储实现耦合(这是一种坏气味):如果希望更改数据存储的实现,那么就需要更改领域模型中对聚合的测试。
下面的代码示例展示了使用被测试对象的状态编写的测试示例。这是在项目中使用的一种测试风格。
public class given_available_seats { private static readonly Guid SeatTypeId = Guid.NewGuid(); private SeatsAvailability sut; private IPersistenceProvider sutProvider; protected given_available_seats(IPersistenceProvider sutProvider) { this.sutProvider = sutProvider; this.sut = new SeatsAvailability(SeatTypeId); this.sut.AddSeats(10); this.sut = this.sutProvider.PersistReload(this.sut); } public given_available_seats() : this(new NoPersistenceProvider()) { } [Fact] public void when_reserving_less_seats_than_total_then_seats_become_unavailable() { this.sut.MakeReservation(Guid.NewGuid(), 4); this.sut = this.sutProvider.PersistReload(this.sut); Assert.Equal(6, this.sut.RemainingSeats); } [Fact] public void when_reserving_more_seats_than_total_then_rejects() { var id = Guid.NewGuid(); sut.MakeReservation(id, 11); Assert.Equal(1, sut.Events.Count()); Assert.Equal(id, ((ReservationRejected)sut.Events.Single()).ReservationId); } }这里展示的两个测试在调用MakeReservation方法后测试可用座位(SeatsAvailability)聚合的状态。第一个用来测试有足够座位可用的场景。第二个用来测试没有足够的座位可用的场景。第二个测试可以利用可用座位(SeatsAvailability)聚合的行为,因为如果该聚合拒绝预订,它确实会引发一个事件。
汇总在旅程的第一阶段,我们探索了实现CQRS模式的一些基础知识,并为下一阶段做了一些准备。
下一章将描述我们如何扩展和增强已经完成的工作,为订单和注册限界上下文添加更多的特性和功能。我们还将研究一些额外的测试技术,以了解它们可能如何帮助我们实现这一目标。