第二种测试方法是通过与MVC控制器类交互来实现。长远的看,这种方法不会那么脆弱,成本就是在最初需要一个更复杂的实现,这需要对系统的内部实现比较熟悉。下面的代码示例展示了这种方法的一个示例。
首先,在Features\UserInterface\Controllers\Registration文件夹下的SelfRegistrationEndToEndWithControllers.feature文件展示了一个示例场景:
Scenario: End to end Registration implemented using controllers Given the Registrant proceeds to make the Reservation And these Order Items should be reserved | seat type | quantity | | General admission | 1 | | Additional cocktail party | 1 | And these Order Items should not be reserved | seat type | | CQRS Workshop | And the Registrant enters these details | first name | last name | email address | | William | Flash | william@fabrikam.com | And the Registrant proceeds to Checkout:Payment When the Registrant proceeds to confirm the payment Then the Order should be created with the following Order Items | seat type | quantity | | General admission | 1 | | Additional cocktail party | 1 | And the Registrant assigns these seats | seat type | first name | last name | email address | | General admission | William | Flash | William@fabrikam.com | | Additional cocktail party | Jim | Corbin | Jim@litwareinc.com | And these seats are assigned | seat type | quantity | | General admission | 1 | | Additional cocktail party | 1 |然后,展示了SelfRegistrationEndToEndWithControllersSteps类里的一些测试步骤:
[Given(@"the Registrant proceeds to make the Reservation")] public void GivenTheRegistrantProceedToMakeTheReservation() { var redirect = registrationController.StartRegistration( registration, registrationController.ViewBag.OrderVersion) as RedirectToRouteResult; Assert.NotNull(redirect); // Perform external redirection var timeout = DateTime.Now.Add(Constants.UI.WaitTimeout); while (DateTime.Now < timeout && registrationViewModel == null) { //ReservationUnknown var result = registrationController.SpecifyRegistrantAndPaymentDetails( (Guid)redirect.RouteValues["orderId"], registrationController.ViewBag.OrderVersion); Assert.IsNotType<RedirectToRouteResult>(result); registrationViewModel = RegistrationHelper.GetModel<RegistrationViewModel>(result); } Assert.False(registrationViewModel == null, "Could not make the reservation and get the RegistrationViewModel"); } ... [When(@"the Registrant proceeds to confirm the payment")] public void WhenTheRegistrantProceedToConfirmThePayment() { using (var paymentController = RegistrationHelper.GetPaymentController()) { paymentController.ThirdPartyProcessorPaymentAccepted( conferenceInfo.Slug, (Guid) routeValues["paymentId"], " "); } } ... [Then(@"the Order should be created with the following Order Items")] public void ThenTheOrderShouldBeCreatedWithTheFollowingOrderItems(Table table) { draftOrder = RegistrationHelper.GetModel<DraftOrder>(registrationController.ThankYou(registrationViewModel.Order.OrderId)); Assert.NotNull(draftOrder); foreach (var row in table.Rows) { var orderItem = draftOrder.Lines.FirstOrDefault( l => l.SeatType == conferenceInfo.Seats.First(s => s.Description == row["seat type"]).Id); Assert.NotNull(orderItem); Assert.Equal(Int32.Parse(row["quantity"]), orderItem.ReservedSeats); } }您可以看到这种方法是如何直接使用RegistrationController类的。
在这些代码示例中,您可以看到是怎样通过标记把SpecFlow feature文件和测试步骤代码链接起来并传递参数的。团队选择使用xUnit.net来实现测试步骤,要在Visual Studio里运行这些测试,您可以使用任何支持xUnit的第三方工具例如:ReSharper, CodeRush, TestDriven.NET等。
Jana(软件架构师)发言:
请记住,这些验收测试并不是在系统上执行的唯一测试。主要的解决方案里包括全面的单元测试和集成测试,测试团队还对应用程序进行了探索性和性能测试。
关于使用CQRS模式和大量使用消息,有一个常见说法是这让人很难理解系统是如何通过发送和接收消息把各个不同的部分配合在一起的。这里您可以通过设计适当的单元测试来帮助别人理解您的基本代码。
订单聚合的第一个单元测试示例:
public class given_placed_order { ... private Order sut; public given_placed_order() { this.sut = new Order( OrderId, new[] { new OrderPlaced { ConferenceId = ConferenceId, Seats = new[] { new SeatQuantity(SeatTypeId, 5) }, ReservationAutoExpiration = DateTime.UtcNow } }); } [Fact] public void when_updating_seats_then_updates_order_with_new_seats() { this.sut.UpdateSeats(new[] { new OrderItem(SeatTypeId, 20) }); var @event = (OrderUpdated)sut.Events.Single(); Assert.Equal(OrderId, @event.SourceId); Assert.Equal(1, @event.Seats.Count()); Assert.Equal(20, @event.Seats.ElementAt(0).Quantity); } ... }这个单元测试只是创建一个Order实例,并直接调用UpdateSeats方法。它不向阅读测试代码的人提供有关调用此方法中命令或事件的任何信息。