第4章 MVC模式 — 精通MVC 3 框架

来源:互联网 发布:sql 二次查询 编辑:程序博客网 时间:2024/06/05 15:02
 

The MVC Pattern
MVC模式

In Chapter 7, we are going to start building a more complex ASP.NET MVC example. Before we start digging into the details of the ASP.NET MVC Framework, we want to make sure you are familiar with the MVC design pattern and the thinking behind it. In this chapter, we describe the following:
在第7章中,我们将开始建立一个更复杂的ASP.NET MVC例子。在深入到ASP.NET MVC框架的细节之前,我们希望确保你熟悉了MVC设计模式,并按它的方式进行思考。本章中,我们将描述下述内容:

  • The MVC architecture pattern
    MVC架构模式
  • Domain models and repositories
    域模型及存储库
  • Creating loosely coupled systems using dependency injection
    用依赖性注入生成松散耦合系统
  • The basics of automated testing
    自动测试基础

You might already be familiar with some of the ideas and conventions we discuss in this chapter, especially if you have done advanced ASP.NET or C# development. If not, we encourage you to read this chapter carefully. A good understanding of what lies behind MVC can help put the features of the framework into context as we continue through the book.
你也许已经熟悉了本章讨论的某些思想和约定,特别是如果你已经完成过高级的ASP.NET或C#开发。如果没有,我们鼓励你仔细阅读本章。良好地理解MVC背后的东西,有助于在阅读本书的过程中把这个框架的特性放到相关的语境中。

The History of MVC
MVC简史

The term model-view-controller has been in use since the late 1970s. It arose from the Smalltalk project at Xerox PARC, where it was conceived as a way to organize some early GUI applications. Some of the fine detail of the original MVC pattern was tied to Smalltalk-specific concepts, such as screens and tools, but the broader concepts are still applicable to applications, and they are especially well suited to web applications.
术语“模型-视图-控制器”自70年代后期就已出现。它产生于Xerox PARC的Smalltalk项目,当时它被构想为早期GUI应用程序的一种组织方式。原始MVC模式的一些很好的细节依赖于Smalltalk特有的概念,如屏幕和工具,但更广泛的概念仍然适用于应用程序,而且它们特别适用于web应用程序。

Interactions with an MVC application follow a natural cycle of user actions and view updates, where the view is assumed to be stateless. This fits nicely with the HTTP requests and responses that underpin a web application.
与一个MVC应用程序的交互符合用户动作和视图更新的自然周期,这里假设视图是无状态的。这十分适合于一个web应用程序的基础 — HTTP请求与响应。

Furthermore, MVC forces a separation of concerns—domain model and controller logic is decoupled from the UI. In a web application, this means that the mess of HTML is kept apart from the rest of the application, which makes maintenance and testing simpler and easier. It was Ruby on Rails that led to renewed mainstream interest in MVC, and it remains the poster child for the MVC pattern. Many other MVC frameworks have since emerged and demonstrated the benefits of MVC—including, of course, ASP.NET MVC.
进一步地,MVC强迫关注分离 — 域模型和控制器逻辑与UI是松耦合关系。在一个web应用程序中,这意味着凌乱的HTML与应用程序的其它部分分离开来了,从而使维护与测试更加简单容易。这是Ruby on Rails成为引导MVC的主流,并使它成为MVC模式的楷模的原因。许多其它MVC框架自MVC出现并表明其优点之后也相继出现 — 当然这也包括ASP.NET MVC。

Understanding the MVC Pattern
理解MVC模式

In high-level terms, the MVC pattern means that an MVC application will be split into at least three pieces:
从高级术语上说,MVC模式意为一个MVC应用程序将被分离成至少三个部分:

  • Models, which contain or represent the data that users work with. These can be simple view models, which just represent data being transferred between views and controllers; or they can be domain models, which contain the data in a business domain as well as the operations, transformations, and rules for manipulating that data.
    模型,包含或表示了用户与之工作的数据。这些可以是简单的视图模型,它只是表现在视图与控制器之间传递的数据;也可以是域模型,它含有业务领域的数据以及处理这些数据的操作、转换和规则。
  • Views, which are used to render some part of the model as a UI.
    视图,用于把模型的某个部分渲染成UI(用户界面)。
  • Controllers, which process incoming requests, perform operations on the model, and select views to render to the user.
    控制器,处理传入的请求,执行模型上的操作,选择渲染给用户的视图。

Models are the definition of the universe your application works in. In a banking application, for example, the model represents everything in the bank that the application supports, such as accounts, the general ledger, and credit limits for customers, as well as the operations that can be used to manipulate the data in the model, such as depositing funds and making withdrawals from the accounts. The model is also responsible for preserving the overall state and consistency of the data; for example, making sure that all transactions are added to the ledger, and that a client doesn’t withdraw more money than he is entitled to or more money than the bank has.
模型是应用程序工作世界的定义。例如,在一个银行应用程序中,模型表示了应用程序所支持的银行中的任何东西,如账号、总账、客户的信用额度等,以及能够用来维护模型中数据的操作,如存款基金以及账号撤消。模型也负责保持整体状态和数据一致性,例如,确保所有业务加入分类账,以及一个客户端不能提取高于其限额的钱或银行现有的钱。

Models are also defined by what they are not responsible for. Models don’t deal with rendering UIs or processing requests—those are the responsibilities of views and controllers. Views contain the logic required to display elements of the model to the user, and nothing more. They have no direct awareness of the model and do not communicate with the model directly in any way. Controllers are the glue between views and the model. Requests come in from the client and are serviced by the controller, which selects an appropriate view to show the user and, if required, an appropriate operation to perform on the model.
模型也由不是它们职责的内容来定义。模型不涉及渲染UI或处理请求 — 那些是视图和控制器的责任。视图含有把模型元素显示给用户的逻辑,其它什么也没有。它们不直接感知模型,也不以任何方式与模型直接通信。控制器是视图与模型之间的粘合剂。请求来自客户端并由控制器进行服务,控制器选择一个相应的视图向用户进行显示,而且如果需要,执行模型上的一个相关的操作。

Each piece of the MVC architecture is well defined and self-contained, which is referred to as the separation of concerns. The logic that manipulates the data in the model is contained only in the model, the logic that displays data is only in the view, and the code that handles user requests and input is contained only in the controller. With a clear division between each of the pieces, your application will be easier to maintain and extend over its lifetime, no matter how large it becomes.
MVC架构的每一部分都是定义良好和自包含的,这称为关系分离。模型中操作数据的逻辑只包含在模型中,显示数据的逻辑只在视图中,而处理用户请求和输入的代码只包含在控制器中。利用每个部分之间清晰的分离,你的应用程序在其生命周期中将更易于维护和扩充,无论它会变得多大。

Understanding the Domain Model
理解域模型

The most important part of an MVC application is the domain model. We create the model by identifying the real- world entities, operations, and rules that exist in the industry or activity that our application must support, known as the domain.
MVC应用程序最重要的部分是域模型。对存在于应用程序必须支持的业务或活动中的真实世界的实体、操作、和规则,我们用对它们进行标识的方法来生成模型,我们称之为域。

We then create a software representation of the domain: the domain model. For our purposes, the domain model is a set of C# types (classes, structs, and so on), collectively known as the domain types. The operations from the domain are represented by the methods defined in the domain types, and the domain rules are expressed in the logic inside of these methods, or as you saw in the previous chapter, by applying C# attributes to the methods. When we create an instance of a domain type to represent a specific piece of data, we create a domain object. Domain models are usually persistent and long-lived. There are a lot of different ways of achieving this, but relational databases remain the most common choice.
然后我们生成这个域的软件表示:域模型。为此目的,域模型是一组C#类型(类、结构等等),统称为域类型。域中的操作由域类型中的方法表示,而域规则表示成这些方法中的逻辑,或者正如你前一章所看到的,通过把C#属性运用到方法上。当我们生成一个域类型的实例来表示一个特定的数据片段时,我们便生成了一个域对象。域模型通常是保持的且是长时间活动的。有很多不同的方法实现这一情况,但关系数据库是最常用的选择。

In short, a domain model is the single, authoritative definition of the business data and processes within your application. A persistent domain model is also the authoritative definition of the state of your domain representation.
简言之,域模型是应用程序中业务数据及其处理的唯一和权威的定义。一个保持的域模型也是域所表现的状态的权威定义。

The domain model approach solves many of the problems that arise in the smart UI pattern. Your business logic is contained in one place. If you need to manipulate the data in your model or add a new process or rule, the domain model is the only part of your application that has to be changed.
域模型方法解决了智能UI模式中出现的许多问题。你的业务逻辑只包含在一个地方。如果你需要操作模型中的数据,或添加新的过程或规则,域模型是你必须对应用程序进行修改的唯一地方。

n Tip A common way of enforcing the separation of the domain model from the rest of an ASP.NET MVC application is to place the model in a separate C# assembly. In this way, you can create references to the domain model from other parts of the application but ensure that there are no references in the other direction. This is particularly useful in large-scale projects. We use this approach in the example we start building in Chapter 7.
提示:强制域模型与ASP.NET MVC应用程序的其余部分分离的一个常用办法是把模型放在一个独立的C#程序集中。在这种方式中,你可以在应用程序的其它部分,生成对这个域模型的引用,但要确保没有在其它方向进行引用。这在大型项目中特别有用。我们从第7章开始把这种方法使用在例子中。

The ASP.NET Implementation of MVC
ASP.NET MVC的实现

In MVC, controllers are C# classes, usually derived from the System.Web.Mvc.Controller class. Each public method in a class derived from Controller is called an action method, which is associated with a configurable URL through the ASP.NET routing system. When a request is sent to the URL associated with an action method, the statements in the controller class are executed in order to perform some operation on the domain model and then select a view to display to the client. Figure 4-1 shows the interactions between the controller, model, and view.
在MVC中,控制器是C#类,通常派生于System.Web.Mvc.Controller类。从Controller(这里指刚才的System.Web.Mvc.Controller,我们自己生成的每一个控制器都是从这个Controller派生而来的 — 译者)派生而来的类中的每一个public方法都称为一个动作方法,它与一个URL相关联,这个URL是通过ASP.NET路由系统可配置的URL。当一个请求被发送到与一个动作方法相关联的URL时,便执行控制器类中的语句,以进行域模型上的一些操作,然后选择一个视图来显示给客户端。图4-1显示了控制器、模型及视图之间的这种交互。

Figure 4-1. The interactions in an MVC application

The ASP.NET MVC Framework provides support for a choice of view engines. Earlier versions of MVC used the standard ASP.NET view engine, which processed ASPX pages using a streamlined version of the Web Forms markup syntax. MVC 3 has introduced the Razor View Engine, which uses a different syntax entirely (described in Chapter 5). Visual Studio provides IntelliSense support for both view engines, making it a simple matter to inject and respond to view data supplied by the controller.
ASP.NET MVC框架提供了选择视图引擎的支持。较早版本的MVC使用标准的ASP.NET视图引擎,它用改进的Web表单标记语法来处理ASPX页面。MVC 3已经引入了Razor视图引擎,它采用了完全不同的语法(第5章描述)。Visual Studio对这两种视图引擎都提供了智能支持,使得把控制器提供的数据注入到视图和对视图进行响应成为一件简单的事。

ASP.NET MVC doesn’t apply any constraints on the implementation of your domain model. You can create a model using regular C# objects and implement persistence using any of the databases, ORM frameworks, or other data tools supported by .NET. Visual Studio creates a /Models folder as part of the MVC project template. This is suitable for simple projects, but more complex applications tend to define their domain models in a separate Visual Studio project. We’ll discuss implementing a domain model later in this chapter.
ASP.NET MVC对域模型的实现没有任何约束。你可以用规则的C#对象生成一个模型,并可以用数据库、ORM框架、或.NET支持的其它数据工具来实现保持。Visual Studio生成一个/Models文件夹作为MVC项目的模板部分。这对简单项目是合适的,但更复杂的应用程序趋向于在不同的Visual Studio项目中定义它们自己的域模型。本章稍后,我们将讨论一个域模型的实现。

Comparing MVC to Other Patterns
MVC与其它模式的比较

MVC isn’t the only software architecture pattern, of course. There are many others, and some of them are, or at least have been, extremely popular. You can learn a lot about MVC by looking at other patterns. In the following sections, we briefly describe different approaches to structuring an application and contrast them with MVC. Some of the patterns are close variations on the MVC theme, while others are entirely different.
当然,MVC并不是唯一的软件架构模式。还有许多其它模式,它们部分是、或甚至已经是非常普及的。你可以通过考察其它模式,更多地了解MVC。在以下小节中,我们主要描述构造应用程序的不同方法,并把它们与MVC进行对比。有些模式十分接近MVC,而其它的一些则完全不同。

We are not suggesting that MVC is the perfect pattern for all situations. We are both proponents of picking the best approach to solve the problem at hand. As you’ll see, there are situations where we feel that some competing patterns are as useful as or better than MVC. We encourage you to make an informed and deliberate choice when selecting a pattern. The fact that you are reading this book suggests that you already have a certain commitment to the MVC pattern, but we think it is always helpful to maintain the widest possible perspective.
我们并不是在建议MVC是所有情况下的完美模式。我们俩都是挑选最好方法解决手头问题的建议者。正如你将看到的,有些情况我们感觉一些模式与MVC一样有用甚至更有用。我们鼓励你在选择模式时做出有见地且深思熟虑的选择。你正在阅读这本书的事实说明你已经对MVC模式有了一定的信奉,但我们认为保持尽可能宽广的视角总是有好处的。

Understanding the Smart UI Pattern
理解“智能UI”模式

One of the most common design patterns is known as the smart UI. Most programmers have created a smart UI application at some point in their careers—we certainly have. If you’ve used Windows Forms or ASP.NET Web Forms, you have, too.
最通用的设计模式之一称为智能UI。大多数程序员有过生成智能UI应用程序的经历 — 我们肯定有过。如果你用过Windows表单或ASP.NET Web表单,那末你也有过。

To build a smart UI application, developers construct a UI, usually by dragging a set of components or controls onto a design surface or canvas. The controls report interactions with the user by emitting events for button presses, keystrokes, mouse movements, and so on. The developer adds code to respond to these events in a series of event handlers, which are small blocks of code that are called when a specific event on a specific component is emitted. In doing this, we end up with a monolithic application, as shown in Figure 4-2. The code that handles the UI and the business is all mixed together, without any separation of concerns at all. The code that defines the acceptable values for a data input, that queries for data or modifies a user account, ends up in little pieces, coupled together by the order in which events are expected.
要建立一个智能UI应用程序,开发者要构造一个UI,通常是把一组组件或控件拖拽到一个设计界面或画布上。控件通常通过对点击按钮、按键、鼠标移动等发出事件与用户进行交互。开发者把代码添加到一系列事件处理程序中以对这些事件作出响应,事件是一小段代码,当一个特定组件上的某个事件被触发时,这段代码被调用。在做这些事情的过程中,我们实现了一个单片式的应用程序,如图4-2所示。处理UI以及业务的代码全部混在一起,根本没有任何关系分离。数据输入可接收值的定义、数据查询或修改用户账号等等小片段代码都按期望事件的顺序联结在一起。

Figure 4-2. The smart UI pattern

The biggest drawback with this design is that it is difficult to maintain and extend. Mixing the domain model and business logic code in with the UI code leads to duplication, where the same fragment of business logic is copied and pasted to support a newly added component. Finding all of the duplicate parts and applying a fix can be difficult. In a complex smart UI application, it can be almost impossible to add a new feature without breaking an existing one. Testing a smart UI application can also be difficult. The only way to test is to simulate user interactions, which is far from ideal and a difficult basis from which to provide full test coverage.
这种设计最大的障碍是它难以维护和扩展。域模型与业务逻辑代码与UI代码混合在一起导致了复制,要复制并粘贴同样的业务逻辑片段以支持新添加的组件。找出所有复制部分并进行修正可能很困难。在一个复杂的智能UI应用程序中,添加一个新特性而不打断已存在的特性几乎是不可能的。测试一个智能UI应用程序也很困难。测试的唯一方法只能是模拟用户交互,这很不理想而且提供完整测试是一件很困难的事情。

In the world of MVC, the smart UI camp is often referred to as an antipattern—something that should be avoided at all costs. This antipathy arises, at least in part, because people come to MVC looking for an alternative after spending part of their careers trying to develop and maintain smart UI applications. That is certainly true for us; we both bear the scars of those long years, but we don’t reject the smart UI pattern out of hand. Not everything is rotten in the smart UI pattern, and there are positive aspects to this approach. Smart UI applications are quick and easy to develop. The component and design tool producers have put a lot of effort into making the development experience a pleasant one, and even the most inexperienced programmer can produce something professional-looking and reasonably functional in just a few hours.
在MVC世界中,智能UI阵营通常被称为对抗模式 — 是无论如何要避免的事情。这种反感产生了(指对MVC的反感?),至少部分产生,因为人们经历了智能UI应用程序的开发与维护很长时间之后,才来到MVC世界,以寻找另一种开发方法。对我们来说肯定是这样的,我们两人很多年都忍受了这样的痛苦,但我们并没有把智能UI拒绝于手头之外。并不是UI模式所有都是腐败的东西,也有积极的方面。智能UI应用程序快速而易于开发。组件和设计工具的制作者们作了很大努力使开发成为一件愉快的事,甚至很多没什么经验的程序员也能只要几个小时就可以制件一些看上去很专业和合理的功能。

The biggest weakness of smart UI applications—maintainability—doesn’t arise in small development efforts. If you are producing a simple tool for a small audience, a smart UI application can be a perfect solution. The additional complexity of an MVC application simply isn’t warranted.
智能UI应用程序最大的弱点 — 可维护性 — 在小型开发中不会出现。如果你在为很少的听众制作一个简单的工具,智能UI应用程序可能是完美的解决方案。MVC应用程序额外的复杂性则是无法保证的。

Finally, smart UIs are ideal for UI prototyping—those design surface tools are really good. If you are sitting with a customer and want to capture the requirements for the look and flow of the interface, a smart UI tool can be a quick and responsive way to generate and test different ideas.
最后,智能UI对实现UI原型是很理想的 — 那些界面设计工具很棒。如果你正在与一个客户沟通,并想捕捉界面的外观和流程的需求,智能UI工具能够以快速响应的方式生成并测试不同的思想。

Applying Domain-Driven Development
运用域驱动开发(DDD)

We have already described how a domain model represents the real world in your application, containing representations of your objects, processes, and rules. The domain model is the heart of an MVC application. Everything else, including views and controllers, is just a means to interact with the domain model.
我们已经描述了一个域模型如何表示应用程序的真实世界,包括对象、过程、以及规则的表示。域模型是一个MVC应用程序的心脏。其它任何东西,包括视图和控制器都只是和这个域模型进行交互的一种手段。

ASP.NET MVC doesn’t dictate the technology used for the domain model. We are free to select any technology that will interoperate with the .NET Framework. and there are many choices. However, ASP.NET MVC does provide us with infrastructure and conventions to help connect the classes in the domain model with the controllers and views, and with the MVC Framework itself. There are three key features:
ASP.NET MVC并不指定域模型所采用的技术。我们可以选择与.NET框架进行交互的任何技术,而且有很多选择。然而,ASP.NET MVC却给我们提供了帮助域模型中的类与控制器和视图以及MVC框架本身进行连接的体系结构和规范,有三个关键特性:

  • Model binding is a convention-based feature that populates model objects automatically using incoming data, usually from an HTML form post.
    模型绑定是一个基于约定的特性,它用输入数据(通常来自于HTML表单的递交)自动地形成模型对象。
  • Model metadata lets you describe the meaning of your model classes to the framework. For example, you can provide human-readable descriptions of their properties or give hints about how they should be displayed. The MVC Framework can then automatically render a display or an editor UI for your model classes into your views.
    模型元数据让你把模型类的含意描述给框架。例如,你可以对它们的属性提供一个人性化可读的描述,或给出如何显示它们的提示。MVC框架然后可以自动地针对你的模型类把一个显示或编辑界面渲染到你的视图之中。
  • Validation, which is performed during model binding and applies rules that can be defined as metadata.
    校验,这是在模型绑定期间并运用可以定义为元数据的规则来执行的。

We briefly touched on model binding and validation when we built our first MVC application in Chapter 3, and we will return to these topics and investigate further in Chapters 17 and 18. For the moment, we are going to put the ASP.NET implementation of MVC aside and think about domain modeling as an activity in its own right. We are going to create a simple domain model using .NET and SQL Server, using a few core techniques from the world of domain-driven development (DDD).
第3章中建立第一个MVC应用程序时,我们主要接触了模型绑定和校验,第17章和第18章,我们将回到这些论题并作进一步研究。这里,我们打算把MVC的ASP.NET实现先放到一边,而考虑把域以它自己的方式模拟成一个活动。我们打算用.NET和SQL Server生成一个简单的域模型,采用域驱动开发(DDD)领域中的几项核心技术。

Modeling an Example Domain
模拟一个例子域

You have probably experienced the process of brainstorming a domain model. It usually involves developers, business experts, and copious quantities of coffee, cookies, and whiteboard pens. After a while, the people in the room converge on a common understanding, and a first draft of the domain model emerges. You might end up with something similar to Figure 4-5, which is the starting point for this example: a simple domain model for an auction application.
你可能经历过域模型讨论的过程。它通常包括了开发人员、业务专家、大量的咖啡、饼干以及白板笔。之后,房间里的人统一于一个共同的理解上,于是域模型的第一个草案产生了。你最后得到的结果可能类似于图4-5,这是本例的起点:一个简单的拍卖域模型。

n Note In our description of coming up with the draft of the domain model, we skipped over the many hours of disagreement and arguing that seems inevitable at this stage in the process. Suffice to say that the developers will spend the first hours askance at demands from the business experts for features that are taken directly from science fiction, while the business experts will express surprise and concern that time and cost estimates for the application are similar to what NASA requires to reach Mars. The coffee is essential in resolving such standoffs. Eventually everyone’s bladder is so full that progress will be made and compromises reached, just to bring the meeting to an end.
注:在域模型草案形成过程的描述中,我们跳过了若干小时的争论与意见不一,这一阶段过程似乎是不可避免的。可以肯定地说,开发者将把他们的第一部分时间消耗在审视业务专家对特性的要求上,这些特性几乎直接来自于科幻小说,而业务专家对应用程序的时间与开销估计所表现出来的惊奇与关注不亚于国家宇航局需要到达火星。解决这种抵触的主要是咖啡。最终,当每个人的膀胱都撑满的时候,事情才会有进展并达成妥协,会议才会结束。

 

Figure 4-5. The first draft model for an auction application

This model contains a set of Members, which each hold a set of Bids. Each Bid is for an Item, and each Item can hold multiple Bids from different Members.
这个模型有一组成员(Member),每个成员都有一组报价(Bid)。每个报价都针对一个物品(Item),而每个物品可以有不同成员的多个报价。

Ubiquitous Language
随用语言

A key benefit of implementing your domain model as a distinct component is that you can adopt the language and terminology of your choice. You should try to find terminology for its objects, operations, and relationships that makes sense not just to developers, but to your business experts as well. We recommend that you adopt the domain terminology when it already exists. For example, if what a developer would refer to as users and roles are known as agents and clearances in the domain, we recommend you adopt the latter terms in your domain model.
把你的域模型作为一个明确的组件来实现的一个关键好处是你可以采用你选择的语言和技术。你应该力图找出模型的对象、操作、以及关系的术语,这些术语要不仅对开发者,也要对业务专家有意义。我们建议当域已经存在时采用域术语。例如,是否把开发者所说的用户和任务称作为该域的代理商和结算,我们建议在你的域模型中用后一术语。

And when modeling concepts that the domain experts don’t have terms for, you should come to a common agreement about how you will refer to them, creating a ubiquitous language that runs throughout the domain model. There are some benefits in this approach.
而当模拟那些域专家还没有术语的概念时,应该对如何引用它们达成一致,生成整个域模型都可使用的一个随用语言。采用这种方式有一些好处。

First, developers tend to speak in the language of the code—the names of classes, database tables, and so on. Business experts don’t understand these terms, nor should they need to. A business expert with a little technical knowledge is a dangerous thing, because he will be constantly filtering his requirements through his understanding of what the technology is capable of. When this happens, you don’t get a true understanding of what the business requires.
首先,开发者趋向于以代码语言说话 — 类、数据库表等的名字。业务专家不理解这些术语,也不需要理解。有一些技术知识背景的业务专家是一件危险的事情,因为他会经常性地用他对技术能做什么的理解来过滤他的需求。当这种情况发生时,你会得不到业务需求的真实理解。

This approach also helps to avoid overgeneralization in an application. Programmers have a tendency to want to model every possible business reality, rather than the specific one that the business requires. In the auction model, we thus might end up replacing members and items with a general notion of resources linked by relationships. When we create a domain model that isn’t constrained to match the domain being modeled, we miss the opportunity to gain any real insight in the business processes. And in the future, we end up representing changes in business processes as awkward corner cases in our elegant but overly-abstract metaworld. Constraints are not limitations. They are insights that direct your development efforts in the right direction.
这种方法也有助于避免一个应用程序的过度一般化。程序员有一种倾向,他想模拟每一种可能的业务事实,而不是业务所需要的特定情况。在这个拍卖模型中,我们也许最终会以一种由关系连接的一般资源说明来代替成员与物品之间的关系。当我们生成一个并不与所模拟的域匹配的域模型时,我们就失去了获取业务过程实际情形的任何机会。进一步地,我们最终会把事务过程的变化以优雅但过于抽象的元世界表示为难以使用的案例。约束并不是限制。它们是指导开发沿正确的方向前进的视点。

The link between the ubiquitous language and the domain model shouldn’t be a superficial one. DDD experts suggest that any change to the ubiquitous language should result in a change to the model. If you let the model drift out of sync with the business domain, you will create an intermediate language that maps from the model to the domain, and that spells disaster in the long term. You’ll create a special class of people who can speak both languages, and they will then start filtering requirements through their incomplete understanding of both languages.
随用语言与域模型之间的连接不应该是肤浅的。DDD专家建议,对随用语言的任何修改都应该导致对模型的修改。如果你让模型偏离了与业务域的同步,你将生成一个从模型映射到域的中间语言,从长远观点看,这是拼写的灾难。你将生成一个特殊的人物类,他们要说两种语言,然后通过他们对两种语言不完整的理解来过滤需求。

Aggregates and Simplification
集合与简化

Figure 4-5 provides a good starting point for our domain model, but it doesn’t offer any useful guidance about implementing the model using C# and SQL Server. If we load a Member into memory, should we also load her Bids and the Items associated with them? And if so, do we need to load all of the other Bids for those Items, and the Members who made those bids? When we delete an object, should we delete related objects, too, and, if so, which ones? If we choose to implement persistence using a document store instead of a relational database, which collections of objects would represent a single document? We don’t know, and our domain model doesn’t give us any of the answers.
图4-5为域模型提供了一个很好的开端,但它对用C#和SQL Server实现该模型并不提供任何有用的指导。如果我们把成员装入内存,那末我们是否应该把她的报价以及与报价相关的物品也装入内存?要是这样,我们是否装入与这些物品相关的所有其它报价,以及形成这些报价的成员?当我们删除一个对象时,我们也应该删除与之相关的对象,如果这样,要删除哪些对象?如果我们选择用文档存储代替关系数据库来实现保持,哪些对象集合表示一个单一的文档?我们不知道,而且我们的域模型也不会给我们任何回答。

The DDD way of answering these questions is to arrange domain objects into groups called aggregates. Figure 4-6 shows how we might aggregate the objects in our auction domain model.
DDD回答这些问题的方法是把域对象分成称为集合的组。图4-6显示了我们也许可以如何集合拍卖域模型中的对象。

Figure 4-6. The auction domain model with aggregates

An aggregate entity groups together several domain model objects. There is a root entity that’s used to identify the entire aggregate, and it acts as the “boss” for validation and persistence operations. The aggregate is treated as a single unit with regard to data changes, so we need to create aggregates that represent relationships that make sense in the context of the domain model, and create operations that correspond logically to real business processes; that is, we need to create aggregates by grouping objects that are changed as a group.
一个集合实体把几个域模型对象组合在一起。有一个根实体用于标识整个集合,而它充当校验和保持操作的“老板”。集合被处理成进行数据修改的一个单一的单元,因此我们需要生成这些集合,它们表示了域模型场景中有意义的关系,并生成与实际业务过程逻辑对应的操作,即,我们需要通过把作为一个组进行修改的对象进行分组的方法来生成集合。

A key DDD rule is that objects outside a particular instance of an aggregate can hold persistent references to only the root entity, not to any other object inside the aggregate (in fact, the identity of a nonroot object needs to be unique only within its aggregate). This rule reinforces the notion of treating the objects inside an aggregate as a single unit.
一个关键的DDD规则是特定集合实例之外的对象只可以保持对根实体的参考,而不是这个集合内部的任何其它对象(事实上,非根对象的标识只在它的集合中需要是唯一的)。这一规则强化了把一个集合中的对象作为一个单一的单元进行处理的概念。

In our example, Members and Items are both aggregate roots, whereas Bids can be accessed only in the context of the Item that is the root entity of their aggregate. Bids are allowed to hold references to Members (which are root entities), but Members can’t directly reference Bids (because they are not).
在我们的例子中,成员与物品都是集合的根,而报价只可以在物品的范围内进行访问,物品是它们(报价与物品)集合的根。允许报价保留对成员(根实体)的参考,但成员不能直接参考报价(因为报价不是根)。

One of the benefits of aggregates is that it simplifies the set of relationships between objects in the domain model. Often, this can give additional insight into the nature of the domain that is being modeled. In essence, creating aggregates constrains the relationships between domain model objects so that they are more like the relationships that exist in the real-world domain. Listing 4-1 illustrates how our domain model might look like when expressed in C#.
集合的好处之一是简化了域模型中对象之间的一组关系。通常,这可以附带地看出被模拟域的自然性质。本质上,生成集合约束了域模型对象之间的关系,因此它们更像存在于真实域中的关系。清单4-1描述了当用C#表示时,我们的域模型可能看上去的样子。

Listing 4-1. The C# Auction Domain Model

public class Member {    public string LoginName { get; set; } // The unique key    public int ReputationPoints { get; set; }}public class Item {    public int ItemID { get; private set; } // The unique key    public string Title { get; set; }    public string Description { get; set; }    public DateTime AuctionEndDate { get; set; }    public IList<Bid> Bids { get; set; }}public class Bid {    public Member Member { get; set; }    public DateTime DatePlaced { get; set; }    public decimal BidAmount { get; set; }}

Notice how we are easily able to capture the unidirectional nature of the relationship between Bids and Members. We have also been able to model some other constraints. For example, Bids are immutable (representing the common auction convention that bids can’t be changed once they are made). Applying aggregation has allowed us to create a more useful and accurate domain model, which we have been able to represent in C# with ease.
注意,我们是如何才能够很容易地匹配Bids与Members之间的单向关系性质的。我们也能够模拟某些其它约束。例如,报价是不可变的(表示了通常的报价规范,即报价一旦报出便不可改变)。运用集合,使我们能够生成更有用且更精确的模型,我们也能够很容易地用C#表示它。

In general, aggregates add structure and accuracy to a domain model. They make it easier to apply validation (the root entity becomes responsible for validating the state of all objects in the aggregate) and are obvious units for persistence. And, because aggregates are essentially the atomic units of our domain model, they are also suitable units for transaction management and cascade deletes from databases.
一般而言,集合对域模型添加了结构化和精确性。集合使域模型更容易运用校验(根实体成为负责校验这个集合中所有对象的状态)而且是保持的明显单元。再者,因为集合是域模型的主要的基本单元,它们也是进行数据库业务管理和层叠删除合适的单元。

On the other hand, they impose restrictions that can sometimes appear artificial, because often they are artificial. Aggregates arise naturally in document databases, but they aren’t a native concept in SQL Server, nor in most ORM tools. To implement them well, your team will need discipline and effective communication.
换句话说,它们利用有时似乎是想象的约束,因为通常它们是想象的。集合自然地出现在文档数据库中,但它们并不是SQL Server中的一个本土概念,也不在大多数ORM工具中。要很好地实现它们,你的团队需要训练和有效的沟通。

Defining Repositories
定义存储库

At some point, we will need to add persistence for our domain model. This will usually be done through a relational, object, or document database. Persistence is not part of our domain model. It is an independent or orthogonal concern in our separation of concerns pattern. This means that we don’t want to mix the code that handles persistence with the code that defines the domain model.
有时,我们需要对域模型添加保持。这通常通过关系数据库、对象数据库、或文档数据库来完成。保持不是域模型的一部分。它是我们关系分离模式中的一个独立的或正交的关注方面。意即,我们不希望把处理保持的代码与定义域模型的代码混合在一起。

The usual way to enforce separation between the domain model and the persistence system is to define repositories. These are object representations of the underlying database (or file store or whatever you have chosen). Rather than work directly with the database, the domain model calls the methods defined by the repository, which in turn makes calls to the database to store and retrieve the model data. This allows us to isolate the model from the implementation of the persistence.
强制域模型与保持系统分离的常用方法是定义存储库。存储库是低层数据库(或文件库、或你所选的任何其它数据库)的对象表示。域模型不是直接与数据库进行工作,而是调用存储库所定义的方法,而存储库反过来调用数据库来存储和接收模型数据。这允许我们把模型从保持的实现中隔离出来。

The convention is to define separate data models for each aggregate, because aggregates are the natural unit for persistence. In the case of our auction, for example, we might create two repositories: one for Members and one for Items (note that we don’t need a repository for Bids, because bids will be persisted as part of the Items aggregate). Listing 4-2 shows how these repositories might be defined.
习惯做法是为每个集合定义独立的数据模型,因为集合是保持的自然单元。例如,在拍卖场合中,我们可以生成两个存储库:一个用于成员,另一个用于物品(注意,我们不需要用于报价的存储库,因为报价将保持为物品集合的一部分)。清单4-2显示了如何定义这些存储库。

Listing 4-2. C# Repository Classes for the Member and Item Domain Classes

public class MembersRepository {    public void AddMember(Member member) { /* Implement me */ }    public Member FetchByLoginName(string loginName) { /* Implement me */ }    public void SubmitChanges() { /* Implement me */ }}public class ItemsRepository {    public void AddItem(Item item) { /* Implement me */ }    public Item FetchByID(int itemID) { /* Implement me */ }    public IList<Item> ListItems(int pageSize,int pageIndex) { /* Implement me */ }    public void SubmitChanges() { /* Implement me */ }}

Notice that the repositories are concerned only with loading and saving data; they don’t contain any domain logic at all. We can complete the repository classes by adding to each method statements that perform the store and retrieve operations for the appropriate persistence mechanism. In Chapter 7, we will start to build a more complex and realistic MVC application, and as part of that process, we’ll show you how to use the Entity Framework to implement your repositories.
注意,存储库只关注载入和保存数据,它们根本不含任何域逻辑。我们可以针对相应的保持机制,给每个执行存储和接收操作的方法添加必要的语句来实现存储库类。第7章中,我们将开始建立一个复杂且逼真的MVC应用程序,并作为那个过程的一部分,我们给你演示如何用实体框架来实现你的存储库。

Building Loosely Coupled Components
建立松耦合组件

As we’ve said, one of most important features of the MVC pattern is that it enables separation of concerns. We want the components in our application to be as independent as possible and to have as few interdependencies as we can manage.
正如我们已经说过的,MVC模式最重要的特性之一是它能够关注分离。我们希望应用程序中的组件尽可能独立,并只有尽可能少的我们可管理的相互依赖性。

In our ideal situation, each component knows nothing about any other component and deals with other areas of the application only through abstract interfaces. This is known as loose coupling, and it makes testing and modifying our application easier.
在理想情况下,每个组件不知道其它组件,而处理应用程序的其它区域只是通过抽象接口。这称为松耦合,它使应用程序更易于测试和修改。

A simple example will help put things in context. If we were writing a component called MyEmailSender to send e-mail messages, we would implement an interface that defines all of the public functions required to send e-mail, which we would call IEmailSender.
一个简单的例子有助于把事情放到场景中。如果我们在编写一个称为MyEmailSender的组件来发送邮件消息,我们就实现了一个接口,它定义了发送邮件所需要的所有公用函数,我们称这个接口为IEmailSender。(意即,IEmailSender是一个接口,MyEmailSender是IEmailSender的一个具体实现。)

Any other component of our application that needs to send e-mail—let’s say a password reset helper called PasswordResetHelper—can then send a message by referring only to the methods in the interface. There is no direct dependency between PasswordResetHelper and MyEmailSender, as shown by Figure 4-7.
应用程序中需要发送电子邮件的任何其它组件 — 如名字为PasswordResetHelper的口令重置辅助程序 — 然后只要通过引用这个接口中的方法就可以发送一份邮件。在PasswordResetHelper和MyEmailSender之间没有直接的依赖性,如图4-7所示。

Figure 4-7. Using interfaces to decouple components
图4-7. 使用接口去耦组件

By introducing IEmailSender, we ensure that there is no direct dependency between PasswordResetHelp and MyEmailSender. We could replace MyEmailSender with another e-mail provider, or even use a mock implementation for testing purposes.
通过引入IEmailSender,我们保证了PasswordResetHelp与MyEmailSender之间没有直接依赖性。我们可以用另一个邮件提供程序来替换MyEmailSender,甚至可以用一个模仿实现进行测试。

n Note Not every relationship needs to be decoupled using an interface. The decision is really about how complex the application is, what kind of testing is required, and what the long-term maintenance is likely to be. For example, we might choose not to decouple the controllers from the domain model in a small and simple ASP.NET MVC application.
注:不是每一个关系都需要用接口来去掉耦合。决策取决于应用程序有多复杂,需要哪种测试,以及长期维护可能是什么。例如,我们对一个小型且简单的ASP.NET MVC应用程序,也许会选择不去掉控制器与域模型的耦合。

Using Dependency Injection
使用依赖性注入

Interfaces help us decouple components, but we still face a problem—C# doesn’t provide a built-in way to easily create objects that implement interfaces, except to create an instance of the concrete component. We end up with the code in Listing 4-3.
接口帮助我们去掉组件耦合,但我们仍然面对一个问题 — C#没有提供内建的方法,以方便地生成实现接口的对象,除非生成具体组件的一个实例。我们停滞于清单4-3所示的代码。

Listing 4-3. Instantiating Concrete Classes to Get an Interface Implementation

public class PasswordResetHelper {    public void ResetPassword() {        IEmailSender mySender = new MyEmailSender();        ...call interface methods to configure e-mail details...        mySender.SendEmail();    }}

We are only part of the way to loosely coupled components. The PasswordResetHelper class is configuring and sending e-mail through the IEmailSender interface, but to create an object that implements that interface, it needed to create an instance of MyEmailSender. 我们只处于松耦合的半途上。PasswordResetHelper类通过IEmailSender接口进行配置并发送电子邮件,但要生成实现这个接口的对象,这需要生成一个MyEmailSender实例。

We have made things worse. Now PasswordResetHelper depends on IEmailSender and MyEmailSender, as shown in Figure 4-8.
我们让事情出现了问题。现在PasswordResetHelper依赖于IEmailSender和MyEmailSender,如图4-8所示。

Figure 4-8. Components are tightly coupled after all

What we need is a way to obtain objects that implement a given interface without needing to create the implementing object directly. The solution to this problem is called dependency injection (DI), also known as inversion of control (IoC).
我们需要的是获得实现一个给定接口的对象的办法,而不需要直接生成这个实现对象。对这个问题的解决方案是依赖性注入(DI),也称为控制反转(IoC)。

DI is a design pattern that completes the loose coupling we started by adding the IEmailSender interface to our simple example. As we describe DI, you might wonder what the fuss is about, but bear with us, because this is an important concept that is central to effective MVC development.
我们一开始把IEmailSender接口添加到我们的简单例子,DI正是实现松散耦合的一种设计模式。在我们对DI进行描述时候,你也许奇怪这么忙乱干嘛,但请稍稍忍耐,因为这是有效开发MVC重要的核心概念。

There are two parts to the DI pattern. The first is that we remove any dependencies on concrete classes from our component—in this case PasswordResetHelper. We do this by passing implementations of the required interfaces to the class constructor, as shown in Listing 4-4.
DI模式有两个部分。第一是我们从组件 — 这里是PasswordResetHelper — 中取消了对具体类的依赖性。实现的办法是,把所需的接口传递给类构造器,如清单4-4所示。

Listing 4-4. Removing Dependencies from the PasswordResetHelper Class

public class PasswordResetHelper {    private IEmailSender emailSender;    public PasswordResetHelper(IEmailSender emailSenderParam) {        emailSender = emailSenderParam;    }    public void ResetPassword() {        ...call interface methods to configure e-mail details...        emailSender.SendEmail();    }}

We have broken the dependency between PasswordResetHelper and MyEmailSender. The PasswordResetHelper constructor demands an object that implements the IEmailSender interface, but it doesn’t know, or care, what the object is and is no longer responsible for creating it.
我们已经打断了PasswordResetHelper与MyEmailSender之间的依赖性。PasswordResetHelper构造器需要一个对象,这个对象实现了IEmailSender接口,但它不知道,也不关心这个对象是什么,而且不再负责生成它。

The dependencies are injected into the PasswordResetHelper at runtime; that is, an instance of some class that implements the IEmailSender interface will be created and passed to the PasswordResetHelper constructor during instantiation. There is no compile-time dependency between PasswordResetHelper and any class that implements the interfaces it depends on.
这种依赖性在运行时被注入到PasswordResetHelper中,即,实现IEmailSender接口的某个类的实例将在实例化期间生成并传递给PasswordResetHelper构造器。在PasswordResetHelper与实现它所依赖的这个接口的任何类之间都不再有编译时的依赖性。

n Note The PasswordResetHelper class demands its dependencies be injected using its constructor. This is known as constructor injection. We could also allow the dependencies to be injected through a public property, known as setter injection.
注:PasswordResetHelper类需要用它的构造器来注入它的依赖性。这称为构造器注入。我们也可以允许通过一个public属性来注入这种依赖性,这称为setter注入(指在此属性的set代码块中实现依赖性注入 — 译者注)。

Because the dependencies are dealt with at runtime, we can decide which interface implementations are going to be used when we run the application. We can choose between different e-mail providers or inject a mocked implementation for testing. We have achieved the dependency relationships we were aiming for.
因为依赖性是在运行时处理的,我们可以在运行应用程序时再决定使用哪个接口实现。我们可以在不同的电子邮件提供程序之间进行选择,或注入一个模仿进行测试。我们已经取得了我们要得到的依赖性关系。

An MVC-Specific Dependency Injection Example
一个MVC专用的依赖性注入例子

Let’s go back to the auction domain model we created earlier and apply DI to it. The goal is to create a controller class, which we’ll call AdminController, that uses the repository MembersRepository for persistence without directly coupling AdminController and MembersRepository together. We’ll start by defining an interface that will decouple our two classes—we’ll call it IMembersRepository—and change the MembersRepository class to implement the interface as shown in Listing 4-5.
让我们回到前面生成的拍卖域模型,并对它运用DI。目的是生成一个控制器类,我们称它为AdminController,它用MembersRepository存储库进行保持,而不把AdminController和MembersRepository耦合在一起。我们从让两个类松耦合的接口定义开始 — 我们称它为IMembersRepository — 并修改MembersRepository类来实现这个接口,如清单4-5所示。

Listing 4-5. The IMembersRepository Interface

public interface IMembersRepository {    void AddMember(Member member);    Member FetchByLoginName(string loginName);    void SubmitChanges();}public class MembersRepository : IMembersRepository {    public void AddMember(Member member) { /* Implement me */ }    public Member FetchByLoginName(string loginName) { /* Implement me */ }    public void SubmitChanges() { /* Implement me */ }}

We can now write a controller class that depends on the IMembersRepository interface, as shown in Listing 4-6.
现在我们根据IMembersRepository接口来编写一个控制器类,如清单4-6所示。

Listing 4-6. The AdminController Class

public class AdminController : Controller {    IMembersRepository membersRepository;    public AdminController(IMembersRepository repositoryParam) {        membersRepository = repositoryParam;    }    public ActionResult ChangeLoginName(string oldLoginParam, string newLoginParam) {        Member member = membersRepository.FetchByLoginName(oldLoginParam);        member.LoginName = newLoginParam;        membersRepository.SubmitChanges();        // ... now render some view    }}

The AdminController class demands an implementation of the IMembersRepository interface as a constructor parameter. This will be injected at runtime, allowing AdminController to operate on an instance of a class that implements the interface without being coupled to that implementation.
AdminController类要求IMembersRepository接口的一个实现作为构造器参数。这将在运行时注入,允许AdminController在一个实现这个接口的类的实例上进行操作,而不必耦合这个实现。

Using a Dependency Injection Container
使用依赖性注入容器

We have resolved our dependency issue: we are going to inject our dependencies into the constructors of our classes at runtime. But we still have one more issue to resolve: how do we instantiate the concrete implementation of interfaces without creating dependencies somewhere else in our application?
我们已经解决了依赖性问题:我们在运行时把依赖性注入到类的构造器之中。但我们还有一些问题需要解决:我们如何实例化接口的具体实现,而无需在应用程序的某个其它地方生成依赖性?

The answer is a DI container, also known as an IoC container. This is a component that acts as a broker between the dependencies that a class like PasswordResetHelper demands and the concrete implementation of those dependencies, such as MyEmailSender.
回答是用DI容器,也称为IoC容器。这是在需要依赖性的类如PasswordResetHelper和这些依赖性的具体实现如MyEmailSender之间担任代理的一个组件。

We register the set of interfaces or abstract types that our application uses with the DI container, and tell it which concrete classes should be instantiated to satisfy dependencies. So, we would register the IEmailSender interface with the container and specify that an instance of MyEmailSender should be created whenever an implementation of IEmailSender is required. Whenever we need an IEmailSender, such as to create an instance of PasswordResetHelper, we go to the DI container and are given an implementation of the class we registered as the default concrete implementation of that interface—in this case, MyEmailSender.
我们用这种DI容器来注册一组我们应用程序要使用的接口或抽象类型,告诉它应该实例化哪些具体类以满足依赖性。因此,我们用这个容器来注册IEmailSender接口,并指定需要实现IEmailSender时,生成一个MyEmailSender实例。无论我们什么时候需要一个IEmailSender,如生成一个PasswordResetHelper实例时,我们进入DI容器,让它生成这个接口注册的默认具体类的实例 — 这里是MyEmailSender。

We don’t need to create the DI container ourselves. There are some great open source and freely licensed implementations available. The one we like is called Ninject and you can get details at http://www.ninject.org. We’ll introduce you to using Ninject in Chapter 6.
我们不需要自己生成这个DI容器。有一些很棒的开源代码并免费使用的实现可以拿来使用。我们喜欢的一个叫Ninject,你可以在http://www.ninject.org上获得细节。我们将在第6章给你介绍Ninject的使用。

n Tip Microsoft created its own DI container, called Unity. We are going to use Ninject, though, because we like it and it demonstrates the ability to mix and match tools when using MVC. If you want more information about Unity, see unity.codeplex.com.
提示:微软生成了它自己的DI容器,称为Unity。但我们打算用Ninject,因为我们喜欢它,而且在使用MVC时,它表现了混合和匹配工具的能力。如果你需要更多关于Unity的信息,参阅unity.codeplex.com。

The role of a DI container may seem simple and trivial, but that isn’t the case. A good DI container, such as Ninject, has some very clever features:
DI容器的作用似乎简单而平常,但事实并不是这样。一个好的DI容器,如Ninject,有一些十分聪明的特性:

  • Dependency chain resolution: If you request a component that has its own dependencies (for example, constructor parameters), the container will satisfy those dependencies, too. So, if the constructor for the MyEmailSender class requires an implementation of the INetworkTransport interface, the DI container will instantiate the default implementation of that interface, pass it to the constructor of MyEmailSender, and return the result as the default implementation of IEmailSender.
    依赖链解析:如果你请求一个有它自己依赖性的组件(如,构造器参数),这个容器也会满足这种依赖性。因此,如果MyEmailSender类的构造器需要一个INetworkTransport接口的实现,DI容器将实例化这个接口的默认实现,把它传递给MyEmailSender的构造器,并以IEmailSender默认实现作为返回结果。
  • Object life-cycle management: If you request a component more than once, should you get the same instance each time or a fresh new instance? A good DI container will let you configure the life cycle of a component, allowing you to select from predefined options including singleton (the same instance each time), transient (a new instance each time), instance-per-thread, instance-per-HTTP-request, instance-from-a-pool, and many others.
    对象生命周期管理:如果你不止一次地请求一个组件,你每次要得到同样的实例还是一个新实例?一个好的DI容器将让你配置一个组件的生命周期,允许你从预定义的选项中进行选择,包括singleton(每次同样的实例)、transient(每次新实例)、instance-per-thread(每线程实例)、instance-per-HTTP-request(每HTTP请求实例)、instance-from-a-pool(应用程序池实例)等等。
  • Configuration of constructor parameter values: If the constructor for the implementation of the INetworkTransport interface requires a string called serverName, for example, you should be able to set a value for it in your DI container configuration. It’s a crude but simple configuration system that removes any need for your code to pass around connection strings, server addresses, and so forth.
    构造器参数值配置:例如,如果对INetworkTransport接口实现的构造器需要一个叫做serverName的字符串,你应该能够在你的DI容器配置中设置一个值。这是笨拙但简单的配置系统,它不需要你用代码来传递诸如连接字串、服务器地址等等之类的参数。

You might be tempted to write your own DI container. We think that’s a great experimental project if you have some time to kill and want to learn a lot about C# and .NET reflection. If you want a DI container to use in a production MVC application, we recommend you use one of the established DI containers, such as Ninject.
你也许对编写自己的DI容易感兴趣。我们认为这是一个很好的实验项目,如果你有时间并且想更多地了解C#和.NET的反射。如果你想把一个DI容器用于一个MVC应用程序产品,我们建议你用一个已经建好的DI容器,如Ninject。

Getting Started with Automated Testing
自动测试初步

The ASP.NET MVC Framework is designed to make it as easy as possible to set up automated tests and use development methodologies such as test-driven development, which we’ll explain later in this chapter. ASP.NET MVC provides an ideal platform for automated testing, and Visual Studio has some great testing features. Between them, they make designing and running tests simple and easy.
ASP.NET MVC框架设计成尽可能容易地建立自动测试和使用诸如测试驱动开发之类的开发方法学,我们将在本章稍后对之进行解释。ASP.NET MVC为自动测试提供了一个理想平台,而Visual Studio有一些很好的测试特性。它们(指MVC和Visual Studio — 译者注)使得在它们之间进行设计和运行测试简单而容易。

In broad terms, web application developers today focus on two kinds of automated testing. The first is unit testing, which is a way to specify and verify the behavior of individual classes (or other small units of code) in isolation from the rest of the application. The second type is integration testing, which is a way to specify and verify the behavior of multiple components working together, up to and including the entire web application.
从广义上讲,今天的web应用程序开发者注重于两种自动测试。第一是单元测试,这是以与应用程序其它部分隔离的方式指定和检验个别类(或其它小型代码单元)行为的方法。第二是集成测试,这是指定并检验多个组件协同工作行为的方法,乃至包括整个web应用程序。

Both kinds of testing can be extremely valuable in web applications. Unit tests, which are simple to create and run, are brilliantly precise when you are working on algorithms, business logic, or other back-end infrastructure.
这两种测试在web应用程序中都有巨大的价值。单元测试便于生成和运行,当你在算法、业务逻辑,或其它后端基础结构上工作时,单元测试是十分精确的。

The value of integration testing is that it can model how a user will interact with the UI, and can cover the entire technology stack that your application uses, including the web server and database. Integration testing tends to be better at detecting new bugs that have arisen in old features; this is known as regression testing.
集成测试的价值是它可以模拟一个用户如何与UI交互,并可以涉及你应用程序的整个技术,包括web服务器和数据库。集成测试更便于在旧的特性中侦测新bugs,这称为回归测试。

Understanding Unit Testing
理解单元测试

In the .NET world, you create a separate test project in your Visual Studio solution to hold test fixtures. This project will be created when you first add a unit test, or can be set up automatically if you create an MVC project using the Internet Application template. A test fixture is a C# class that defines a set of test methods, one method for each behavior you want to verify. A test project can contain multiple test fixture classes.
在.NET世界中,你在Visual Studio解决方案中生成一个独立的测试项目来容纳测试装置。当你第一次添加一个单元测试时,这个项目便生成,或者,如果你用Internet应用程序模板生成一个MVC项目时,这个测试项目便自动生成。一个测试装置是一个C#类,它定义了一组测试方法,每个方法对应于你希望检验的每个行为。一个测试项目可以含有多个测试装置类。

n Note We’ll show you how to create a test project and populate it with unit tests in Chapter 6. The goal for this chapter is just to introduce the concept of unit testing and give you an idea of what a test fixture looks like and how it is used.
注:我们将在第6章中向你演示如何生成一个测试项目并用单元测试组装它。本章的目的只是介绍单元测试概念并给出测试装置看上去像什么以及如何使用它的思想。

Listing 4-7 contains an example test fixture that tests the behavior of the AdminController.ChangeLoginName method, which we defined in Listing 4-6.
清单4-7含有一个测试装置的例子,它测试AdminController.ChangeLoginName方法的行为,这是我们在清单4-6中定义的方法。

Listing 4-7. An Example Test Fixture

[TestClass]public class AdminControllerTest {     [TestMethod]     public void CanChangeLoginName() {          // Arrange (set up a scenario)          Member bob = new Member() { LoginName = "Bob" };          FakeMembersRepository repositoryParam = new FakeMembersRepository();          repositoryParam.Members.Add(bob);          AdminController target = new AdminController(repositoryParam);          string oldLoginParam = bob.LoginName;          string newLoginParam = "Anastasia";          // Act (attempt the operation)          target.ChangeLoginName(oldLoginParam, newLoginParam);          // Assert (verify the result)          Assert.AreEqual(newLoginParam, bob.LoginName);          Assert.IsTrue(repositoryParam.DidSubmitChanges);     }     private class FakeMembersRepository : IMembersRepository {          public List<Member> Members = new List<Member>();          public bool DidSubmitChanges = false;          public void AddMember(Member member) {               throw new NotImplementedException();          }          public Member FetchByLoginName(string loginName) {               return Members.First(m => m.LoginName == loginName);          }          public void SubmitChanges() {               DidSubmitChanges = true;          }     }}

The test fixture is the CanChangeLoginName method. Notice that the method is decorated with the TestMethod attribute and that the class it belongs to, called AdminControllerTest, is decorated with the TestClass attribute. This is how Visual Studio finds the test fixture.
该测试装置是CanChangeLoginName方法。注意,该方法以TestMethold属性进行修饰,而且它隶属的类,AdminControllerTest,以TestClass属性来修饰。这是Visual Studio如何查找测试装置的。

The CanChangeLoginName method follows a pattern known as arrange/act/assert (A/A/A). Arrange refers to setting up the conditions for the test, act refers to performing the test, and assert refers to verifying that the result was the one that was required. Being consistent about the structure of your unit test methods make them easier to read—something you’ll appreciate when your project contains hundreds of unit tests.
CanChangeLoginName方法遵循一种叫做“布置/动作/断言(A/A/A)”的模式。布置是指建立测试环境,动作是指执行测试,而断言是指检查结果。让单元测试方法有一致的结构,使它们更易于阅读 — 当你的项目含有数百个单元测试时,你会欣赏这种做法。

The test fixture in Listing 4-7 uses a test-specific fake implementation of the IMembersRepository interface to simulate a specific condition—in this case, when there is a single member, Bob, in the repository. Creating the fake repository and the Member are done in the arrange section of the test.
清单4-7所示的测试装置使用了IMembersRepository接口的一种测试特有的模仿实现,以模仿特定的条件 — 即,存储库中有一个成员,Bob,时的情况。生成模仿的存储库和成员是在测试的布置部分完成的。

Next, the method being tested, AdminController.ChangeLoginName, is called. This is the act section of the test. Finally, we check the results using a pair of Assert calls; this is the assert part of the test. We run the test by using the Visual Studio Test menu, and get visual feedback about the tests as they are performed, as shown in Figure 4-9.
接着,是调用测试方法,AdminController.ChangeLoginName。这是测试的动作部分。最后,我们用一对Assert调用检查其结果,这是测试的断言部分。我们用Visual Studio的测试菜单来运行这个测试,当测试执行后,得到这些测试的可视化反馈,如图4-9所示。

Figure 4-9. Visual feedback on the progress of unit tests

If the test fixture runs without throwing any unhandled exceptions, and all of the Assert statements pass without problems, the Test Results window shows a green light. If not, you get a red light and details of what went wrong.
如果测试装置运行后没弹出任何未处理异常,并且所有断言都通过而没有问题,测试结果窗口显示绿色信号。否则,你得到红色信号和问题细节。

n Note You can see how our use of DI has helped us with unit testing. We were able to create a fake implementation of the repository and inject it into the controller to create a very specific scenario. We are big fans of DI and this is one of the reasons.
注:你可以看到,DI的使用是如何帮助我们进行单元测试的。我们可以生成存储库的模仿实现并把它注入到控制器中,以生成一个特殊的场景。我们是DI的超级粉丝,这也是使用DI的原因之一。

It might seem like we’ve gone to a lot of effort to test a simple method, but it wouldn’t require much more code to test something far more complex. If you find yourself considering skipping small tests like this one, consider that test fixtures like this one help to uncover bugs that can sometimes be hidden in more complex tests.
看上去我们对一个简单的测试已经做了很多努力,但对一些更复杂很多的测试,我们也不需要太多的代码了。如果你发现自己正考虑跳过这种小型测试,那么请考虑使用像这样的测试装置来帮助你揭示有时可能隐藏在更复杂的测试中的程序缺陷。

As we go through the book, you’ll see examples of more complex and concise tests. One improvement we can make is to eliminate test-specific fake classes like FakeMembersRepository by using a mocking tool. We’ll show you how to do this in Chapter 6.
随着我们阅读本书,你会看到一些更复杂的例子和简洁测试。我们可以做的改进之一是用一个模仿工具来取消专用的测试模仿类,如这里的FakeMembersRepository。第6章我们将演示怎么做。

Using Test-Driven Development and the Red-Green-Refactor Workflow
使用测试驱动开发及“红-绿-重构”工作流

With test-driven development (TDD), you use unit tests to help design your code. This can be an odd concept if you are used to testing after you have finished coding, but there is a lot of sense in this approach. The key concept is a development workflow called red-green-refactor. It works like this:
利用测试驱动开发(TDD),你用单元测试帮助设计你的代码。如果你习惯于已经完成编码之后再进行测试,这可能是一个古怪的概念,但这种方法很有意义。关键概念是一种叫“红-绿-重构”的开发工作流。它像这样工作:

  1. Determine that you need to add a new feature or method to your application.
    确定你需要给应用程序添加的一个新特性或方法。
  2. Write the test that will validate the behavior of the new feature when it is written.
    编写这个新特性时编写检验其行为的测试。
  3. Run the test and get a red light.
    运行这个测试并得到一个红色信号。
  4. Write the code that implements the new feature.
    编写实现这个新特性的代码。
  5. Run the test again and correct the code until you get a green light.
    再次运行测试并修正代码直到你得到一个绿色信号。
  6. Refactor the code if required. For example, reorganize the statements, rename the variables, and so on.
    必要时重构这个代码。例如,重组语句、重命名变量等等。
  7. Run the test to confirm that your changes have not changed the behavior of your additions.
    运行测试以确认你的修改已经不改变你所添加的这个行为。

This workflow is repeated for every feature you add.
对你添加的每个特性重复这种工作流。

Let’s walk through an example so you can see how it works. Let’s imagine the behavior we want is the ability to add a bid to an item, but only if the bid is higher than all previous bids for that item. First, we will add a stub method to the Item class, as shown in Listing 4-8.
让我们漫游一个示例,以使你能够明白这是如何工作的。让我们想象,我们所希望的行为是对一个物品添加报价的能力,但只当这个报价高于这个物品之前的所有报价。首先,我们将对物品类添加一个占位方法,如清单4-8所示。

Listing 4-8. Adding a Stub Method to the Item Class

using System;using System.Collections.Generic;namespace TheMVCPattern.Models {    public class Item {        public int ItemID { get; private set; } // The unique key        public string Title { get; set; }        public string Description { get; set; }        public DateTime AuctionEndDate { get; set; }        public IList<Bid> Bids { get; private set; }        public void AddBid(Member memberParam, decimal amountParam) {            throw new NotImplementedException();        }    }}

It’s obvious that the AddBid method, shown in bold, doesn’t display the required behavior, but we won’t let that stop us. The key to TDD is to test for the correct behavior before implementing the feature. We are going to test for three different aspects of the behavior we are seeking to implement:
显然,AddBid方法,黑体显示,并不显示所需要的行为,但我们并不让它使我们停下来。TDD的关键是在实现这个特性时测试它的正确行为。我们打算对要实现的行为测试三个不同的方面:

  • When there are no bids, any bid value can be added.
    当没有报价时,可以添加任何报价值
  • When there are existing bids, a higher value bid can be added.
    当已经有报价时,可以添加一个较高值的报价
  • When there are existing bids, a lower value bid cannot be added.
    当已经有报价时,不能添加一个较低值的报价

To do this, we create three test methods, which are shown in Listing 4-9.
为了实现它,我们生成三个测试方法,如清单4-9所示。

Listing 4-9. Three Test Fixtures

[TestMethod()]public void CanAddBid() {    // Arrange - set up the scenario    Item target = new Item();    Member memberParam = new Member();    Decimal amountParam = 150M;    // Act - perform the test    target.AddBid(memberParam, amountParam);    // Assert - check the behavior    Assert.AreEqual(1, target.Bids.Count());    Assert.AreEqual(amountParam, target.Bids[0].BidAmount);}[TestMethod()][ExpectedException(typeof(InvalidOperationException))]public void CannotAddLowerBid() {    // Arrange    Item target = new Item();    Member memberParam = new Member();    Decimal amountParam = 150M;    // Act    target.AddBid(memberParam, amountParam);    target.AddBid(memberParam, amountParam - 10);}[TestMethod()]public void CanAddHigherBid() {    // Arrange    Item target = new Item();    Member firstMember = new Member();    Member secondMember = new Member();    Decimal amountParam = 150M;    // Act    target.AddBid(firstMember, amountParam);    target.AddBid(secondMember, amountParam + 10);    // Assert    Assert.AreEqual(2, target.Bids.Count());    Assert.AreEqual(amountParam + 10, target.Bids[1].BidAmount);}

We’ve created a unit test for each of the behaviors we want to see. The test methods follow the arrange/act/assert pattern to create, test, and validate one aspect of the overall behavior. The CannotAddLowerBid method doesn’t have an assert part in the method body because a successful test is an exception being thrown, which we assert by applying the ExpectedException attribute on the test method.
对我们希望看到的每个行为,我们都生成了一个单元测试。这些测试方法遵循了“布置/行为/断言”模型来生成、测试和检验整个行为的一个方面。CannotAddLowerBid方法在方法体中并没有断言部分,因为一个成功的测试是一个被弹出的异常,我们通过运用测试方法的ExpectedException属性来断言。

n Note Notice how the test that we perform in the CannotAddLowerBid unit test method will shape our implementation of the AddBid method. We validate the result from the test by ensuring that an exception is thrown and that it is an instance of System.InvalidOperationException. Writing a unit test before you write the code can help you think about how different kinds of outcomes should be expressed before you get bogged down in the implementation.
注:注意,我们在CannotAddLowBid单元测试方法中的测试要如何描述AddBid方法的实现。我们用确保弹出异常的方法来检验测试的结果,而该异常是System.InvalidOperationExeption的一个实例。在编写代码之前编写单元测试可以帮助你在陷入实现过程的困境之前,去思考如何表示不同的结果种类。

As we would expect, all of these tests fail when we run them, as shown in Figure 4-10.
正如我们预期的,当我们运行它们时,所有这些测试都会失败,如图4-10所示。

Figure 4-10. Running the unit tests for the first time

We can now implement our first pass at the AddBid method, as shown in Listing 4-10.
现在我们可以在AddBid方法上实现我们第一个测试通过,如清单4-10所示。

Listing 4-10. Implementing the AddBid Method

using System;using System.Collections.Generic;namespace TheMVCPattern.Models {    public class Item {        public int ItemID { get; private set; } // The unique key        public string Title { get; set; }        public string Description { get; set; }        public DateTime AuctionEndDate { get; set; }        public IList<Bid> Bids { get; set; }        public Item() {            Bids = new List<Bid>();        }         public void AddBid(Member memberParam, decimal amountParam) {            Bids.Add(new Bid() {                BidAmount = amountParam,                DatePlaced = DateTime.Now,                Member = memberParam            });        }    }}

We’ve added an initial implementation of the AddBid method to the Item class. We’ve also added a simple constructor so we can create instances of Item and ensure that the collection of Bid objects is properly initialized. Running the unit tests again generates better results, as shown in Figure 4-11.
我们把AddBid方法的一个初始化实现加到了物品类。我们也添加了一个简单的构造器,以使我们能够生成物品的实例,并保证报价对象的集合被适当地初始化。再次运行测试单元得到了更好的结果,如图4-11所示。

Figure 4-11. Running unit tests against our initial implementation

Two of the three unit tests have passed. The one that has failed is CannotAddLowerBid. We didn’t add any checks to make sure that a bid is higher than previous bids on the item. We need to modify our implementation to put this logic in place, as shown in Listing 4-11.
三个单元测试中的两个已经通过。还失败的一个是CannotAddLowerBid。我们还没有添加任何检查以确保一个报价比该物品之前的报价更高。我们需要修改我们的实现,在适当的地方放置这个逻辑,如清单4-11所示。

Listing 4-11. Improving the Implementation of the AddBid Method

using System;using System.Collections.Generic;using System.Linq;namespace TheMVCPattern.Models {    public class Item {        public int ItemID { get; private set; } // The unique key        public string Title { get; set; }        public string Description { get; set; }        public DateTime AuctionEndDate { get; set; }        public IList<Bid> Bids { get; set; }         public Item() {            Bids = new List<Bid>();        }        public void AddBid(Member memberParam, decimal amountParam) {            if (Bids.Count() == 0 || amountParam > Bids.Max(e => e.BidAmount)) {                Bids.Add(new Bid() {                    BidAmount = amountParam,                    DatePlaced = DateTime.Now,                    Member = memberParam                });            } else {                throw new InvalidOperationException("Bid amount too low");            }        }    }}

You can see that we have expressed the error condition in such a way as to satisfy the unit test we wrote before we started coding; that is, we throw an InvalidOperationException when a bid is received that is too low.
你可以看到,在我们开始编写代码之前,我们已经以这种方式表示了满足单元测试的错误条件,即,当接收到一个太低的报价时,弹出一个InvalidOperationException异常。

n Note We have used the Language Integrated Query (LINQ) feature to check that a bid is valid. Don’t worry if you are not familiar with LINQ or the lambda expression we used (the => notation). We’ll give you an introduction to the C# features that are essential to MVC development in Chapter 5.
注意,我们已经使用了语言集成查询(LINQ)特性来检查一个报价是有效的。如果你不熟悉LINQ或我们使用的lambda表达式(=>记号),不用着急。我们将在第5章向你介绍这种C#特性,这些特性是MVC开发的基础。

Each time we change the implementation of the AddBid method, we run our unit tests again. The results are shown in Figure 4-12.
这次我们修改AddBid方法的实现,再次运行单元测试。结果如图4-12所示。

Figure 4-12. Successful unit test results

Success! We have implemented our new feature such that it passes all of the unit tests. The last step is to take a moment and be sure that our tests really do test all aspects of the behavior or feature we are implementing. If so, we are finished. If not, then we add more tests and repeat the cycle, and we keep going until we are confident that we have a comprehensive set of tests and an implementation that passes them all.
成功!我们已经实现了所有单元测试都通过的这个新特性。最后一步是花一些时间并确保我们的测试真正测试了我们要实现的方法或特性的所有方面。如果这样,我们就完成了。如果不是,那么我们添加更多测试,并重复这一循环并继续,直到我们确信我们有了一组综合测试并全部通过了的实现。

This cycle is the essence of TDD. There is a lot to recommend it as a development style, not least because it makes a programmer think about how a change or enhancement should behave before the coding starts. You always have a clear end point in view and a way to check that you are there. And if you have unit tests that cover the rest of your application, you can be sure that your additions have not changed the behavior elsewhere.
这种循环是TDD的精髓。有很多人建议把它作为一种开发循环,不仅是因为它使一个程序员在开始编码之前就思考如何进行修改和增强。你总是有一个期待的目标,并且有一种方法在那里进行检查。而且如果你有了涉及应用程序其余部分的单元测试,你就可以保证你的添加已经不会改变其它地方的行为。

GETTING THE UNIT TEST RELIGION
形成单元测试信仰

If you don’t currently unit test your code, you might find the process awkward and disruptive—more typing, more testing, more iterations. If you do perform unit tests, you already know what a difference it makes: fewer bugs, better-designed software, and fewer surprises when you make a change.
如果你当前对代码不作单元测试,你或许会发觉开发过程笨拙而混乱 — 输入越多、测试越多、条件越多。如果你进行了单元测试,你已经知道它会产生什么不同:更少的bug、更好的软件设计、以及在你进行修改时更少出现奇怪现象。

Going from a nontester to a tester can be tough. It means adopting a new habit and sticking with it long enough to get the benefits. Our first few attempts to embrace testing failed because of unexpected shifts in due dates. It’s hard to convince yourself that doing something that feels like extra work is worthwhile when time is tight.
从一个非测试者到一个测试者可能是艰苦的。这意味着要采取一种新的习惯并忍耐它足够的时间才能得到好处。由于意外的转换,我们最初的一些尝试在一定时间内会遭遇失败。当时间很紧时,要做某些感觉多余的工作很难让你确信这是有价值的。

We have both become adherents of unit testing and are convinced that it is a great style of development. ASP.NET MVC is an ideal candidate for adopting unit testing if you’ve never tried before, or if you’ve tried and given up. The Microsoft team has made unit testing incredibly easy by separating the key classes from the underlying technology, which means you can create mock implementation of key features and test corner-case situations that would be incredibly difficult to replicate otherwise. We’ll show you examples of unit testing MVC applications throughout this book. We encourage you to follow along and try unit testing for yourself.
我们俩(指两位作者 — 译者注)都已经成为单元测试的信徒,并且承认这是一种伟大的开发风格。如果你以前从未尝试或者你曾经尝试但打算放弃,ASP.NET MVC是进行单元测试的理想选择。微软团队已经通过把关键类从现有技术中分离出来的办法使易于单元测试达到难以置信的程度,这意味着你可以对关键特性生成模仿实现,并测试其它方式下难以重复的各种情况。我们将通过这本书给你演示单元测试MVC应用程序的例子。我们鼓励你遵循单元测试并试着自己单元测试。

Understanding Integration Testing
理解集成测试

For web applications, the most common approach to integration testing is UI automation. This term refers to simulating or automating a web browser to exercise the application’s entire technology stack by reproducing the actions that a user would perform, such as clicking buttons, following links, and submitting forms. The following are the two best-known open source browser automation options for .NET developers:
对web应用程序来说,集成测试最常用的办法是UI自动化。此术语是指通过再现用户要执行的行为的办法,模拟或自动操作一个web浏览器来练习应用程序的整个技术堆栈,如,点击按钮、跟随超连接、以及递交表单等。以下是.NET开发者的两个最著名的开源浏览器自动化工具:

  • Selenium RC (http://seleniumhq.org/), which consists of a Java “server” application that can send automation commands to Internet Explorer, Firefox, Safari, or Opera, plus clients for .NET, Python, Ruby, and multiple others so that you can write test scripts in the language of your choice. Selenium is powerful and mature; its only drawback is that you have to run its Java server.
    Selenium RC (http://seleniumhq.org/),它含有一个Java的“服务器”应用程序,它可以把自动化命令发给IE、Firefox、Safari、或Opera、.NET客户端、Python、Ruby、以及许多其它浏览器,以使你可以用你所选的语言来编写测试脚本。Selenium功能强大且成熟,它唯一的缺点是你必须在它的Java服务器上运行。
  • WatiN (http://watin.sourceforge.net/), a .NET library that can send automation commands to Internet Explorer or Firefox. Its API isn’t as powerful as Selenium, but it comfortably handles most common scenarios and is easy to set up (you need to reference only a single dynamic-link library).
    WatiN(http://watin.souceforge.net),这是一个可以把自动化命令发给IE或Firefox的.NET库。它的ASPI不如Selenium那么功能强大,但它舒适地处理最常见的情形,而且它很容易建立(只需要引用一个动态链接库)。

Integration testing is an ideal complement to unit testing. While unit testing is well suited to validating the behavior of individual components at the server, integration testing lets you create tests that are client-focused, re-creating the actions of a user. As a result, it can highlight problems that come from the interaction between components—hence the term integration testing. And since integration testing for a web application is done through the browser, you can test that JavaScript behaviors work the way they are supposed to, which is very difficult with unit testing.
集成测试是单元测试的一种理想的补充。当单元测试很合适地检验单个组件在服务器上的行为后,集成测试让你生成了致力于客户端的测试,再生了用户的行为。结果,它可以标记出组件之间的交互所出现的问题 — 因此我们称为集成测试。而且,由于web应用程序的集成测试是通过浏览器完成的,你可以测试JavaScript的行为,单元测试对此是十分困难的。

There are some drawbacks, too. Integration testing takes more time. It takes longer to create the tests and longer to perform them. And integration tests can be brittle. If you change the ID attribute of a component that is checked in a test, the test will fail.
也有一些缺点。集成测试花费更多的时间。生成它们需要更长的时间,执行它们也需要更长的时间。而且集成测试可能是脆弱的。如果你修改了在一个测试中需要检查的一个组件的ID属性,测试就可能失败。

As a consequence of the additional time and effort required, integration testing is often done at key project milestones—perhaps after a weekly source code check-in, or when major functional blocks are completed. Integration testing is every bit as useful as unit testing, and it can highlight problems that unit testing can’t. The time required to set up and run integration testing is worthwhile, and we encourage you to add it to your development process.
由于需要额外的时间和努力,集成测试通常在项目的关键点上实施 — 可能是一周的源代码审查、也可能是主要功能块完成时。集成测试与单元测试一样有用,而且它能够突出单元测试不能的问题。建立和运行集成测试的时间是值得的,而且我们鼓励你把它纳入你的开发过程。

We aren’t going to get into integration testing in this book. That’s not because we don’t think it is useful—it is, which is why we urged you to add it to your process—but because it goes beyond the focus of this book. The ASP.NET MVC Framework has been specifically designed to make unit testing easy and simple, and we need to include unit testing to give you the full flavor of how to build a good MVC application. Integration testing is a separate art, and what is true when performing integration testing on any web application is also true for MVC.
本书中我们不打算进行集成测试。这不是因为我们不认为它有用 — 它是有用的,这是我们怂恿你把它纳入你的过程原因 — 而是因为它超出了本书的焦点。ASP.NET MVC框架作了特别设计以使单元测试容易而简单,而我们需要涵盖单元测试,给你品尝如何建立一个良好的MVC应用程序的完全风味。集成测试是一种不同的艺术,而且,当在任何web应用程序上执行集成测试为真的东西对MVC亦为真。

Summary
摘要

In this chapter, we introduced you to the MVC architectural pattern and compared it to some other patterns you may have seen or heard of before. We discussed the significance of the domain model and created a simple example. We also introduced DI, which allows us to decouple components to enforce a strict separation between the parts of our application.
本章中,我们介绍了MVC架构模式,并将其与你可能见过或听说过的其它模式进行了比较。我们讨论了域模型的重要性,并生成了一个简单的例子。我们也介绍了DI,它允许我们去除组件耦合,以强迫应用程序各部件之间的严格分离。

We demonstrated some simple unit tests, and you saw how decoupled components and DI make unit testing simple and easy. Along the way, we demonstrated our enthusiasm for TDD and showed how we write the unit tests before we write our application code. Finally, we touched upon integration testing and compared it to unit testing.
我们演示了一些简单的单元测试,你也看到了松耦合组件以及DI使单元测试简易。沿着这种方式,我们示范了对TDD的热爱,并演示了如何在编写代码之前编写单元测试。最后,我们接触了集成测试,并将其与单元测试作了比较。

原创粉丝点击