Übersetzung vorschlagen
 
Andere Vorschläge:

progress indicator
Keine anderen Vorschläge
MSDN Magazin > Home > Ausgaben > 2009 > MSDN Magazin Juli 2009 >  MVC getestet: Erstellen testbarer ASP.NET MVC-A...
Inhalt anzeigen:  Englisch mit deutscher ÜbersetzungInhalt anzeigen: Englisch mit deutscher Übersetzung
Dies sind maschinell übersetzte Inhalte, die von den Mitgliedern der Community bearbeitet werden können. Sie können die Übersetzung verbessern, indem Sie auf den jeweils zum Satz gehörenden Link "Bearbeiten" klicken.Mithilfe des Dropdown-Steuerelements "Inhalt anzeigen" links oben auf der Seite können Sie zudem bestimmen, ob nur der englische Originaltext, nur die deutsche Übersetzung oder beides nebeneinander angezeigt werden.
Testable MVC
Building Testable ASP.NET MVC Applications
Justin Etheredge

This article discusses:
  • ASP.NET MVC
  • Building testable solutions
  • Dependency injection
This article uses the following technologies:
ASP.NET
You've probably heard the old adage, "Better, faster, cheaper, pick any two." If you want something good and fast, it isn't going to be cheap, and if you want something fast and cheap, it isn't going to be very good. Cheaper, faster, and better means that we need to write more code more quickly, right? If only it were that simple. Learning to type faster might satisfy two of the requirements, but it isn't going to make the software you develop any better. So how do we make software better? What does "better" mean?
"Better" means producing flexible, maintainable software with a low number of defects; better software is all about long-term maintainability. To achieve this, a key design decision is to ensure that components are loosely coupled. Loosely coupled software has many benefits. One that stands out is that it improves our ability to test solutions. If we write software that can be broken down easily into small parts, it becomes easier to test. It sounds simple when you word it that way, but the amount of software in use today that is difficult to test or maintain shows that it is not as straightforward as we might like to think. Software needs to be coupled to do anything useful, but developers need tools and techniques to decrease coupling so that solutions are easier to test.

Testing Support in ASP.NET MVC
We are seeing something of a renaissance in testing tools and techniques. Everywhere you look you find test frameworks and tools such as mocking containers and dependency injection frameworks. Not all these tools' purpose is for testing, but in the end they allow us to build applications that are much more testable.
From a general perspective, a testable application is an application that is loosely coupled enough to allow its independent parts to be tested in isolation. Writing an application that is testable requires a lot of design work from the start, but to build testable applications, developers must also use tools and frameworks that are designed around paradigms that are easily testable. If you are not working with tools that allow you to build testable applications, no amount of good design will help. Thankfully, ASP.NET MVC was designed from the ground up to be testable, and several key design choices were made to allow developers to create testable applications.
Some would say that the Model View Controller (MVC) pattern is inherently more testable than the Page Controller pattern (the pattern employed by ASP.NET Web Forms) because it encourages separation of the application flow logic and the display logic. In the MVC pattern, the logic that controls the flow of the application resides inside the controller classes, so a developer can easily instantiate and execute this logic as though it were any other .NET class. This allows developers to easily execute the business logic using a simple unit test in much the same way they would test any other POCO (Plain Old CLR Object) class.
Another choice the development team at Microsoft made was to allow many pieces of the framework to be pluggable. Among the more important pieces are the controller factories, which control the instantiation of controller classes. Allowing developers to replace or extend controller factories makes it possible to execute logic that can resolve and inject the dependencies of the controllers. Allowing for the dependencies of a controller to be injected at run time is key to creating more loosely coupled and testable applications.
In traditional ASP.NET, one of the obstacles that developers come across during testing is the plethora of static classes used during each request. The ASP.NET MVC team made the decision to wrap many of the .NET static helper classes (such as HttpContext and HttpRequest) so that they can be replaced during testing with a stub. ASP.NET MVC provides many abstractions to help developers avoid using these classes, but in places where you are required to use them, the wrappers make this code easier to test.

Even though ASP.NET MVC provides many of the tools developers need to create testable applications, you cannot rely on it to guide you in the right direction. Instead, you must design your applications deliberately to support testing, and a few additional tools help with this:
  • Unit Testing Framework. xUnit.NET by Brad Wilson and Jim Newkirk. xUnit provides a way to run automated unit tests. Many people use MSTest, but many other unit testing frameworks are out there, and I encourage you to take a look at some of them. I chose xUnit.NET because it is simple, easily extended, and has a very clean syntax. I'm using the xUnit test runner for Resharper, but the xUnit GUI test runner is in the Tools folder of the sample application.
  • Dependency Injection Framework. Ninject 2 by Nate Kohari. Ninject provides a way to wire up classes in your application. I'll describe this approach in more depth later in the article.
  • Mocking Framework. Moq by Clarius Consulting. Moq provides a framework for mocking interfaces and classes during testing.

Application Scenario
To show how to design a testable application, I'll use a simple application scenario in which lists of categories and products are displayed on a Web page. The application also includes simple screens to view, edit, and create categories and products. The simple schema, shown in Figure 1, consists of a one-to-many mapping between categories and products.
Figure 1 Schema for the Sample Application(Click the image for a larger view)

High-Level Application Design
When you create an application, the initial design can go a long way toward helping or hindering the application's long-term health—its maintainability and testability. The approach taken in many architectures is to find the optimal amount of abstraction without creating too much overhead. The MVC pattern already provides some architectural guidance by defining three layers for an application. Some developers might think that these three levels provide enough abstraction to produce large applications. Unfortunately, that often isn't the case, and as you will see, the model can easily be more than a single layer.
You must remember that in many small applications, including the sample for this article, three levels of abstraction is probably sufficient. In these instances, having some views, controllers, and a set of classes to interact with is likely enough. However, several additional layers of abstraction are needed to create a highly testable application. Many architectural patterns at our disposal can help in forming the overall design, including the MVC pattern.
ASP.NET MVC Code-Behind Pages
If you were following ASP.NET MVC before the final release, you may have noticed that the code-behind pages have been removed from the framework. These pages served no functionality in the ASP.NET MVC framework and promoted putting logic into views, where it does not belong. Any logic contained in views is difficult to test in much the same way that code-behind files in ASP.NET Web Forms are difficult to test.
Most of you reading this article are probably familiar with the pattern, but you might not have thought about its implications for application testability. The first part of the MVC pattern is the view, which contains logic for rendering data to a client. Originally, this was a user interface, but a client can be a Web browser, a Web service, client-side JavaScript, and so on. The view should be used only to render data for a consumer, and the amount of logic needed should be abstracted into helper classes as much as possible.
The model represents the back end of an application. This part of the application, which is very loosely defined in the ASP.NET MVC implementation of the pattern, can take on many different forms, and it will very likely depend on the scope of your application. In a small application with little complex business logic, for example, this layer might simply be a set of business objects using the Active Record pattern that you interact with directly in the controller layer.
The controller orchestrates the flow of the application by taking data from the model and passing it to the appropriate view. Since this class is separate from the display logic, by employing a few techniques you should be able to instantiate this class in a unit test and have no dependencies on the ASP.NET runtime. This enables complete testing of the controller without having to run inside a Web server.

Abstracting the Model
When looking at these layers, you might see a few places where additional abstraction would make it easier to test an application. This is common with more complex applications, one area being between the controller and the model. If you have the layering I described earlier, your controller actions might look something like the following:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formCollection)
{
    Category category = Category.FindById(id);
    category.Name = formCollection["name"];
    category.Save();            
    return RedirectToAction("List");
}
The challenges here are at least twofold. First, you cannot easily test this code without having a database connection. Because we are using the Active Record pattern, our data access code is coupled to the domain entity, so we cannot easily swap it out. Also, if you have business logic that is not entirely contained in the domain entity, you could start leaking business or application logic into the controller class. This might lead to duplication of code or even worse—bugs stemming from inconsistencies in implementation. This can hinder testability because you now need to make sure you are testing the same behavior in multiple places within the application.

The Repository Pattern
The next step in improving the testability of this design is to leverage the Repository pattern, which is detailed by Martin Fowler in his book Patterns of Enterprise Application Architecture. The Repository pattern is often misinterpreted as a layer that sits between the application and a database, but in fact it is a layer that sits between your application and any sort of persistent storage. This pattern confines all your data access to a few key places, allowing you to remove it to facilitate testing application logic in isolation.
Implementations of the Repository pattern provide a view of your persistent storage that appears to be an in-memory repository. The interface to such a repository could be similar to this:
public interface ICategoryRepository
{
    IEnumerable<Category> FindAll();
    void Insert(Category category);
    Category FindById(int id);
    void Update(Category category);
}
Testing in Isolation
Some people might think that testing in isolation is not important and choose to only write integration tests. A testing framework such as MSTest or NUnit can still execute integration tests, but they test multiple layers of the application, including making calls across the network, accessing the disk, persisting data to a database, and so on. These sorts of tests are very important and should be present in all applications, but as an application grows, these tests become very slow and can be brittle on different developer machines. Separating unit tests and integration tests allows you to run the unit tests quickly and reliably on developer machines while running your integration tests regularly on your build server.
When you begin to separate classes from one another and test in isolation, you'll find that testing becomes much less brittle and much easier. When you mock a dependency, you control exactly what it returns, and you can more easily test the edge cases of the calling class. Also, because you aren't relying on multiple levels of dependencies, you don't need to account for as much when you are preparing for your test scenarios. Just keep in mind that testing classes in isolation is not a replacement for writing integration tests to make sure that all components work well together.
The important thing to note here is that you are moving the data persistence out of the domain entity and into a separate class, which you can replace more easily during testing. Applying the pattern to the Edit Action shown earlier would look like Figure 2.

Injecting the Repository
The code in Figure 2 looks clean, but how would you replace the repository class during testing? That's easy. Instead of instantiating the class inside the method, you pass it in the class. In this case, you can pass it in as a constructor parameter. The problem, however, is that you don't control the instantiation of the controller classes. To solve this problem, you can use the dependency injection framework that I referenced earlier. Numerous frameworks are available on the market. I chose to use Ninject 2. (If you aren't familiar with dependency injection, see the article in the September 2005 issue of MSDN Magazine by Griffin Caprio, "Dependency Injection." As I said, the first step in using dependency injection is to pass the CategoryRepository into the controller via the controller's constructor:
public CategoryController(ICategoryRepository categoryRepository)
{
    this.categoryRespository = categoryRepository;
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formCollection)
{
    var categoryRepository = new CategoryRepository();
    Category category = categoryRepository.FindById(id);
    category.Name = formCollection["name"];
    categoryRepository.Update(category);

    return RedirectToAction("List");
}
Next, you need to tell Ninject that whenever it sees an ICategoryService interface, it needs to inject an instance of the CategoryService. Every dependency injection framework has a different method of configuration, and Ninject uses a code-based configuration instead of the more usual XML. Because this binding is very straightforward, the code to implement it is just one line:
Bind<ICategoryRepository>().To<CategoryRepository>().InTransientScope();
This statement tells the dependency injection framework to bind ICategoryRepository to CategoryRepository in a transient scope, which means that each time an ICategoryRepository is requested, you get a new instance of CategoryRepository. This is in contrast to something like a singleton scope, where you continue to get back the same instance of the repository.
With the controller refactored so that the repository is passed in through the constructor, you can now test the behavior of the controller independently of the database. One way to accomplish this is by writing a dummy class (commonly referred to as a stub), which implements the ICategoryRepository interface. You can then implement the methods you need to return dummy data and perform dummy operations, although this seems like a lot of work. In addition, what happens when you need to return more than one set of dummy data for a single method in different tests? You would end up with multiple implementations of the stub, or flags in the stub, and that might be a significant amount of extra effort.
Here is where a good mocking framework can come in. A mocking framework allows you to create mock objects. Mock objects allow developers to emulate, through interfaces or virtual methods, the behavior of another class during testing and then verify that expected events occurred. The behavior of mock objects (also simply called mocks) is what allows you to replace dependencies in your classes and then test those classes without having the real dependencies in place.
This might be a bit confusing, but you need the mocking framework to let you do some stubbing. Yes, that's right. Because a mock is an object that allows you to assert that a certain action has been performed on it, and a stub provides dummy data or actions, it should be clear that in most real-world scenarios you want a mixture of the two. Given this fact, most mocking frameworks have functionality that allows you to stub out methods or properties without asserting any behavior. (If you are still a bit confused about mocks versus stubs, Martin Fowler has a good article on the topic, "Mocks Aren't Stubs."
The code in Figure 3 shows a test that mocks the ICategoryRepository interface, stubs out the FindAll method so that it returns a dummy list of categories, and then passes the mocked instance to the controller class. Then the List method on the controller class is called, and the result can be asserted. In this case, the test is asserting that the model is an IEnumerable of Category and that there is one category in the list.
[Fact]
public void ListActionReturnsListOfCategories()
{
    // Arrange

    // create the mock
    var mockRepository = new Mock<ICategoryRepository>();

    // create a list of categories to return
    var categories = new[] { new Category {Id = 1, Name = "test"} };

    // tell the mock that when FindAll is called,
    // return the list of categories
    mockRepository.Setup(cr => cr.FindAll()).Returns(categories);

    // pass the mocked instance, not the mock itself, to the category
    // controller using the Object property
    var controller = new CategoryController(mockRepository.Object);

    // Act
    var result = (ViewResult) controller.List();

    // Assert
    var listCategories = Assert.IsAssignableFrom<IEnumerable<Category>>(result.ViewData.Model);
    Assert.Equal(1, listCategories.Count());            
}
At this point, the application is looking good. We have a repository that can be replaced during testing and a method for mocking this repository so that we can test the controller behavior in isolation. For some applications, this might be all that's required with respect to the controllers, but many medium to large applications still have another layer of abstraction in place.

Fine-Grained Services
In a more complex application, you might take a more fine-grained service approach and have a set of task or event classes that represent the actions that can be performed in the system. This can have a twofold effect. First, it can reduce the size and complexity of your service classes. These classes can grow quite large if they are not contained. Second, it gives you more control over dependencies because now you only need to worry about the dependencies of individual actions rather than the dependencies of your entire service.
Implementing the Service Layer
A service layer sits on top of an application's domain model and provides a set of operations that can be performed against it. This gives you a place to centralize logic that belongs in your application but might not necessarily belong inside the domain model—logic that would have otherwise likely leaked into the controller's methods. For example, what if you needed to do a security check around the code in Figure 2? You don't want to perform the operation in the action method (although you might under certain circumstances) because reusing this action in another place would require you to bring the security along with it. Or, what if you wanted to put a transaction around an operation? You certainly don't want this logic inside the controller class.
Instead, you can provide a layer of classes that define an application's services and use those classes to perform different actions. In a simple application, such as the sample in this article, the service layer might very closely mimic the methods on the repository, as you can see in the following code, but if the application had more business logic, these methods would likely represent business actions rather than basic CRUD methods:
public interface ICategoryService
{
    IEnumerable<Category> FindAll();
    Category FindById(int id);
    void Save(Category category);
}
For the first two methods in the service, calls are delegated to the repository, and then the results are returned, but you might notice that the services have a Save method instead the Update and Delete methods on the repository. Since the application is able to determine whether an object has been saved, the decision to call Update or Insert on the repository can be left up to the service.
Inserting the service layer into the mix greatly reduces the coupling between the controller classes and the repository classes while keeping your controllers lighter because you can push a lot of logic into the services. The design of the controller classes must be changed to reflect the use of services instead of repositories, so instead of injecting repositories into these classes, you inject the services. In turn, you inject the repositories into the services. This might sound complicated, but with dependency injection, you won't even notice—it all gets wired up for you.
The entire dependency injection configuration (including the Product repository and service) looks like Figure 4.
public class DefaultModule: NinjectModule
{
    public override void Load()
    {
        Bind<ICategoryService>().To<CategoryService>().InTransientScope();
        Bind<ICategoryRepository>().To<CategoryRepository>().InTransientScope();

        Bind<IProductService>().To<ProductService>().InTransientScope();
        Bind<IProductRepository>().To<ProductRepository>().InTransientScope();
    }
}
Now that we are passing repositories and services through constructors, you need to construct the controllers using the dependency injection framework. The dependency injection framework handles the construction and injection of the dependencies, but you need to ask for the controller class from the framework. This is much easier than you might anticipate because the ASP.NET MVC team made the controller factories pluggable. You simply need to implement a controller factory as in Figure 5, and you can then easily replace the default instantiation of the controller classes.
public class NinjectControllerFactory : DefaultControllerFactory
{
    private readonly IKernel kernel;

    public NinjectControllerFactory(IKernel kernel)
    {
        this.kernel = kernel;
    }

    protected override IController GetControllerInstance(Type controllerType)
    {
        return (IController)kernel.Get(controllerType);
    }
}
Since the controller factory inherits from the default implementation, you just need to override the GetControllerInstance method and then request the type from the Ninject kernel, which is the class that controls object instantiation in Ninject. When the kernel receives a controller type, Ninject self-binds (tries to construct) the type because it is a concrete type. If the kernel receives a CategoryController type, the constructor will have a parameter of type ICategoryService. Ninject looks to see if it has a binding for this type, and when it finds the type, it performs the same action, looking for a constructor. The Ninject kernel instantiates a CategoryRepository and passes it to the constructor for ControllerService. Then it passes the ControllerService object to the constructor for the CategoryController. All this happens inside the dependency injection container simply by asking for the type.
The controller factory must be registered for it to work. Registration requires the addition of only a single line to the global.asax file:
public void Application_Start()
{
    // get the Ninject kernel for resolving dependencies
    kernel = CreateKernel();

    // set controller factory for instantiating controller and injecting dependencies
    ControllerBuilder.Current.SetControllerFactory(new NinjectController Factory(kernel));
}
Now, when the user requests a URL and ASP.NET MVC tries to create a controller, it actually ends up being constructed by the dependency injection container. When the controller is tested, you can mock the service to test the controller in isolation, as shown in Figure 6.
[Fact]
public void ListActionReturnsListOfCategories()
{
    // Arrange
    var mockService = new Mock<ICategoryService>();
    var categories = new[] { new Category { Id = 1, Name = "test" } };
    mockService.Setup(cr => cr.FindAll()).Returns(categories);
    var controller = new CategoryController(mockService.Object);

    // Act
    var result = (ViewResult) controller.List();

    // Assert
    var listCategories = Assert.IsAssignableFrom<IEnumerable<Category>>(result.ViewData.Model);            
    Assert.Equal(1, listCategories.Count());
}

Isolating View Logic
You might think at this point that you have enough layers to allow for complete testability. However, we have not yet explored the complexity of views. Imagine a scenario in which you have a product class with a price attribute. (The sample application shows this.) Now let's say that you want to display this price to the user formatted as currency. Some developers might say you should put this requirement in your view, as shown here:
<%= Html.Encode(String.Format("{0:c}", this.Model.Price)) %>
I personally don't like this approach because I can't easily conduct unit tests on this logic when the field is being formatted in the view. (I don't mind using a UI testing framework, but I want to get as much of my testing as possible done in my unit tests.) One solution is to add a property on the product class to format the price for display, but I don't like doing that because it means that the concerns of my view are starting to leak into the domain layer. Something as simple as a price format might not seem like a big deal, but problems always seem to increase as your application grows in size, and small items start to cause issues. For example, what happens if you need to display a price differently on two pages? Will you start adding overloads for each different version to your domain object?
One good approach is to use the presentation model pattern. Presentation models can be mapped to and from domain objects and can hold view-specific logic and values that can easily be tested. In the sample application, a presentation model has been created for each form. Since most of the forms are quite similar, they inherit from shared base classes. In a more complex application, however, they would likely contain very different code and logic depending on the form.
If a presentation model were employed, the previous statement would instead look like this:
<%= Html.Encode(this.Model.DisplayPrice) %>
You could easily test this property in a unit test like that shown in Figure 7. Note that the DisplayPrice was placed on the abstract base class, so a stub was created that inherits from this base class to facilitate testing. Remember, a stub is just an object that returns fake data used for testing. Since you cannot instantiate an abstract class, you use the stub to test the base functionality.
[Fact]
public void PriceIsFormattedAsCurrency()
{
    // Arrange
    var product = new Product {Price = 9.22m};
    var presentationProduct = new 
        PresentationProductStub(product);

    // Act
    string cost = presentationProduct.DisplayPrice;

    // Assert
    Assert.Equal("$9.22", cost);
}
The PresentationProductStub class descends from the PresentationProductBase class, which maps to and from the Product class. This causes the List action for the ProductController class to look like the following:
public ActionResult List()
{
    var products = productService.FindAll();
    return View(new ProductList().MapList(products));
}
The products are being pulled normally from the service, but before they are sent to the view, they are passed to a method on the ProductList class that converts the list of Product classes into a list of ProductList classes. Using a model for your views has several distinct advantages. First, as you already saw, you can add view-specific behavior to your class that can easily be unit tested. This is useful for formatting or transforming data for display. Second, you can use the default model binders built into ASP.NET MVC and place your presentation model on the parameter list of a POST method, as shown here:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ProductCreate productCreate)
{            
    productService.Save(productCreate.GetProduct());
    return RedirectToAction("List");            
}
If you are not aware of how the ASP.NET MVC default model binder works, it simply maps fields in the HTML by name onto properties of the class that are in the parameter list. If I put the Product class in the parameter list in the preceding code, a user could maliciously POST a piece of data named "Id" and overwrite the key on the model. This is obviously not a good thing. A user could similarly match up the names of any field on my class and overwrite the values. Using models on the views allows you to use the default model binders (if you choose) while still being able to control which properties are mapped to your domain object.
The alternative is to define mapping classes that you can use to map entities coming in through action method parameters to entities that you want to save to the database. This allows you to control which properties to copy before saving to the database. Another option is to create a reflection-based mapping tool that you could use to map the fields that you want, similar to the AutoMapper tool created by Jimmy Bogard.

Testing Routes
Now, with many of the high-level architectural tips for testing out of the way, let's focus on more fine-grained testing approaches. The first step in implementing an ASP.NET MVC application is to write tests to verify the behavior of the routes within the application. It is important to ensure that the base route "~/" will forward to the appropriate controller and action, and that other controllers and actions forward correctly as well. It is extremely important to have these tests in place from the beginning so that later, as more routes are added to your application, you are guaranteed that routes that were already defined are not broken.
Start by defining the test for the default route (see Figure 8). In this application, the default controller is defined to be the "category" controller, and the default action in this controller is set to "list". Our test should check the base route and assert that the correct values are contained in the route data.
[Fact]
public void DefaultUrlRoutesToCategoryControllerAndListAction()
{
    // Arrange
    var context = ContextHelper.GetMockHttpContext("~/");
    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);

    // Act
    RouteData routeData = routes.GetRouteData(context.Object);

    // Assert
    RouteTestingHelper.AssertRouteData(routeData, "Category", "List", "");
}
This test is using a helper function to assert the values of the default route, shown here:
public static void AssertRouteData(RouteData routeData, 
    string controller, string action, string id)
{
    Assert.NotNull(routeData);
    Assert.Equal(controller, routeData.Values["controller"]);
    Assert.Equal(action, routeData.Values["action"]);
    Assert.Equal(id, routeData.Values["id"]);
}
Using this same general test pattern, you can pass any route that follows the Controller/Action/Id pattern (such as "~/Product/Edit/12") to the HttpContext and assert the values (see Figure 9).
[Fact]
public void ProductEditUrlRoutesToProductControllerAndListAction()
{
    // Arrange
    var context = ContextHelper.GetMockHttpContext("~/Product/Edit/12");
    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);

    // Act
    RouteData routeData = routes.GetRouteData(context.Object);

    // Assert
    RouteTestingHelper.AssertRouteData(routeData, "Product", "Edit", "12");
}
With this method, the tests for routes are a little verbose (for clarity of learning), and they could certainly be shortened. Ben Scheirman has done some interesting work in this area, and he has a good post on his blog about making very terse route assertions ("Fluent Route Testing in ASP.NET MVC").

Passing Dependencies of Actions as Parameters
The second tip is to pass all dependencies to controller actions as parameters to the action method. I illustrate this with the file download example in the sample application. As I mentioned earlier, an HttpRequest in the ASP.NET runtime is a static class that is incredibly hard to replace or mock during unit testing. In ASP.NET MVC, wrapper classes are provided to allow these classes to be mocked, but the process of mocking them or stubbing them out can still be complex.
To mock an HttpRequestBase object, you need to mock the HttpContextBase object that it sits on and then fill in several properties for each. For the file-saving problem, you need to mock the HttpServerUtilityBase that sits on HttpContextBase. Instead of trying to create multiple mock objects to mimic all the different pieces of the HttpContextBase, it would be nice to just make the HttpServerUtilityBase a parameter of the action method and then pass in that single mocked class during testing:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, ViewProduct viewProduct, 
    HttpServerUtilityBase server, HttpPostedFileBase imageFile)
{
Notice that the third parameter in the preceding code is the type that we need to use, and the type that we need to mock. With the method signature like this, we only need to mock the HttpServerUtilityBase class. If we had accessed this class through this.HttpContext.Server, it would have been necessary to mock the HttpContextBase class as well. The problem is that just adding the HttpServerUtilityBase class to the method parameters won't make it work; you have to provide a way to tell ASP.NET MVC how to instantiate this class. This is where the framework's model binders come in. Note that HttpPostedFileBase already has a custom model binder assigned to it by default.
Model binders are classes that implement the IModelBinder interface and provide a way for ASP.NET MVC to instantiate types on action methods. In this case, we need a model binder for the HttpServerUtilityBase. We can create a class that looks like the following:
public class HttpServerModelBinder: IModelBinder
{
    public object BindModel(ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        return controllerContext.HttpContext.Server;
    }
}
The model binder has access to both the controller's context and a binding context. This model binder simply accesses the controller's context and returns the HttpServerUtilityBase class on the HttpContext class. All that is left is to tell the ASP.NET MVC runtime to use this model binder when it finds a parameter of type HttpServerUtilityBase. Note that this code is placed in the Global.asax file as so:
public void Application_Start()
{
    // assign model binder for passing in the 
    // HttpServerUtilityBase to controller actions
    ModelBinders.Binders.Add(typeof(HttpServerUtilityBase), new HttpServerModelBinder());
}
Now, when an HttpServerUtilityBase is passed as a parameter to an action method, the ASP.NET MVC runtime invokes the BindModel method on the HttpServerModelBinder to get an instance of that class. It is very simple to create model binders, and they make testing controller actions much easier.

Testing with Action Results
One of the early complaints about ASP.NET MVC concerned the difficulty of testing operations such as which view was being rendered from an action method. You had to mock the controller context, a view engine, and so on. It was really painful. In preview 3, the ASP.NET MVC team added the concept of action results, which are classes that descend from the ActionResult class and represent a task that the action is going to perform. Now, in ASP.NET MVC, every action method returns the type ActionResult, and a developer can choose from a number of built-in action-result types or create his or her own. This helps with testing because you can invoke an action method and inspect the result to see what occurred. Let's take the example of a ViewResult that we can create by invoking the View method on the base Controller class, as shown here:
public ActionResult List()
{
    IEnumerable<Category> categories = categoryService.FindAll();
    return View(ViewCategory.MapList(categories));
}
In this action method, a list of categories is passed as the model to the view, but no view name is provided. This means that this action will render the default view named List. If you want to make sure that this action always returns an empty view name, you can write a test like Figure 10.
[Fact]
public void ListActionReturnsViewResultWithDefaultName()
{
    // Arrange
    var mockService = new Mock<ICategoryService>();
    var controller = new CategoryController(mockService.Object);

    // Act
    var result = controller.List();

    // Assert
    var viewResult = Assert.IsType<ViewResult>(result);            
    Assert.Empty(viewResult.ViewName);
}
This test mocks the CategoryService to create the controller class. Then the test invokes the List action to get the ActionResult object. The test finally asserts that the result is of type ViewResult and the name of the view is empty. It would be very easy to test the view name against some known string value, or in cases where the return type is something a bit more complex (JSON format, for example), to check the Data property on the result to ensure that it contains the expected information as returned by a call to the action method.

Recommended Reading
  • Agile Principles, Patterns, and Practices in C# by Robert C. Martin and Micah Martin (Prentice Hall, 2006)
  • Patterns of Enterprise Application Architecture by Martin Fowler (Addison-Wesley Professional, 2002)
  • Microsoft .NET: Architecting Applications for the Enterprise by Dino Esposito and Andrea Saltarello (Microsoft Press, 2008)
Wrapping Up
The ASP.NET MVC team has invested a great deal of effort into creating a flexible architecture that allows for easy testing. Developers can now more easily test their systems because of features like pluggable controller factories, action result types, and wrappers around the ASP.NET context types. All this provides ASP.NET MVC developers with a good starting point, but the onus is still on developers to design and construct applications that are loosely coupled and testable. I hope this article helps as you progress down this path. If you are interested, I've listed these in the Recommended Reading sidebar.

Justin Etheredge is a Microsoft C# MVP, author at CodeThinked.com, and founder of the Richmond Software Craftsmanship Group. He is a senior consultant at Dominion Digital in Richmond, Virginia, where he provides guidance in designing and building systems of all shapes and sizes.

MVC zum Testen
Erstellen testbarer ASP.NET MVC-Anwendungen
Justin Etheredge

Themen in diesem Artikel:
  • XML-Webdienste MVC
  • Erstellen von Lösungen getestet
  • Abhängigkeitsinjektion
In diesem Artikel werden die folgenden Technologien verwendet:
ASP.NET
Sie haben wahrscheinlich die alte Sprichworts, "besser, schneller und billiger, Kommissionierung nur zwei." gehört Wenn Sie etwas gut und schnelle möchten, soll es ist nicht billig werden, und wenn Sie etwas schnell und kostengünstig möchten, soll es ist nicht sehr gut. Günstiger, schneller und besser bedeutet, die wir mehr Code schreiben schneller, rechts müssen? Wenn nur das einfache wurden. Lernen, schneller geben möglicherweise erfüllen zwei der Anforderungen, aber es ist nicht zu der Software, die Sie entwickeln eine besser wechseln. Wie verbessern wir Software? Was bedeutet "besser"bedeuten?
"Besser"bedeutet, dass erzeugen flexible, verwaltbarer Software mit einer niedrigen Anzahl Codefehler;bessere Software geht es um langfristige Wartbarkeit. Um dies zu erreichen, ist es eine wichtige Entwurfsentscheidung, sicherzustellen, dass die Komponenten lose verknüpft sind. Lose gekoppelte Software hat viele Vorteile. Eine abhebt, verbessert, dass unsere Möglichkeit, Lösungen zu testen ist. Wenn wir Software, die mühelos in kleine Teile unterteilt werden kann erstellen, wird es einfacher zu testen. Es klingt einfach, wenn Sie Sie word auf diese Weise, sondern die benötigte Software heute, ist schwierig zu testen oder verwalten angezeigt, die er nicht so einfach wie wir denken möchten möglicherweise verwendet. Software, nützliche Aktionen kombiniert werden muss, aber Entwickler benötigen Tools und Techniken zum Verringern der Kopplung, sodass Lösungen einfacher sind zu testen.

Testen Support in XML-Webdienste MVC
Wir sind etwas ein Renaissance in Tests Tools und Techniken sehen. Überall Sie suchen finden Sie Test-Frameworks und Tools wie mocking Container und Dependency Injection-Frameworks. Nicht alle diese toolsam Ende uns Anwendungen erstellen, die viel besser prüfbare ermöglichen, aber dient zum Testen.
Aus der Perspektive eines allgemeinen testbare Anwendung ist eine Anwendung, lose gekoppelte genug, um seine unabhängige Teile isoliert getestet werden können. Schreiben einer Anwendung, die getestet werden kann viel Entwurfsarbeit ab dem Anfang erfordert, aber testbare Anwendungen zu erstellen, Entwickler müssen auch verwenden, Tools und Frameworks, die um Paradigmen dienen, die leicht getestet werden. Wenn Sie nicht mit Tools, die testbaren Anwendungen ermöglichen arbeiten, wird kein Betrag guter Entwurf helfen. Glücklicherweise XML-Webdienste MVC wurde Einrichten von Grund auf neu entwickelt, getestet werden und mehrere wichtige Entwurfsentscheidungen wurden vorgenommen, damit Entwickler testbare Anwendungen erstellen.
Einige würden sagen, dass das Muster Model View Controller (MVC) grundsätzlich mehr als das Page Controller-Muster (das Muster XML-Webdienste Webformulare zumindest dort angestellt) testbar ist da diese Trennung von der Anwendungslogik Datenfluss und die Anzeige Logik fördert. In MVC-Muster befindet sich in die Controller-Klassen, die die Logik, die den Fluss von der Anwendung steuert, damit ein Entwickler problemlos instanziieren und diese Logik, Ausführen als handele es andere .NET-Klasse. Dadurch können Entwickler problemlos ausführen die Geschäftslogik verwenden einen einfachen Komponententest in ähnlich wie Sie andere POCO (einfache alte CLR Object)-Klasse testen möchten.
Eine andere Wahl das Produktentwicklungsteam bei Microsoft gestellt war, viele Teile des Frameworks austauschbare werden können. Zu wichtiger Teile gehören der Controller Factorys, die die Instanziierung von Klassen Controller gesteuert. Ermöglicht Entwicklern, ersetzen oder Erweitern der Controller Factorys ermöglicht die Logik ausführen, können zu beheben und einfügen die Abhängigkeiten der Domänencontroller. Ermöglicht die Abhängigkeiten eines Domänencontrollers zur Laufzeit eingefügt ist entscheidend für weitere lose gekoppelten und testbaren Anwendungen erstellen.
In herkömmlichen XML-Webdienste, eines Hindernisse, die während des Testens Entwickler stoßen ist der Fülle von statischen Klassen bei jeder Anforderung verwendet. XML-Webdienste MVC-Team versucht die Entscheidung, viele der statischen Hilfsfunktion .NET Klassen (z. B. HttpContext und HttpRequest) umbrochen, so dass Sie während der Tests mit einem Stub ersetzt werden können. XML-Webdienste MVC bietet viele Abstraktionen, die diese Klassen vermeiden Entwicklern dabei helfen, aber an Orten, in denen Sie deren Verwendung erforderlich sind, die Wrapper vereinfachen diesen Code testen.

Obwohl ASP.NET MVC viele der Tools Entwickler testbare Anwendungen erstellen müssen bietet, können nicht Sie darauf, die Sie in die richtige Richtung verlassen. Stattdessen müssen Sie Ihre Anwendungen absichtlich Tests entwerfen, und ein paar zusätzliche Tools helfen, mit diesem:
  • Einheit testen Framework. xUnit.NET von Brad Wilson und Jim Newkirk. xUnit bietet eine Möglichkeit, automatisierte Komponententests auszuführen. Viele Personen verwenden MSTest, aber viele andere Einheit testen Rahmenstrukturen sind draußen, und fangen Sie betrachten einige davon. Ich habe die xUnit.NET, da Sie einfach und problemlos erweitert, und eine sehr saubere Syntax hat. Ich verwende die xUnit-Tests Läufer für Resharper und die xUnit GUI Test Läufer in im Ordner der Beispielanwendung.
  • Dependency Injection-Framework. Ninject 2 von Nate Kohari. Ninject bietet eine Möglichkeit, Klassen in Ihrer Anwendung verbinden. Ich werde dies später in diesem Artikel ausführlicher beschreiben.
  • Framework imitieren. Moq durch Clarius Consulting. Moq bietet ein Framework für Schnittstellen und Klassen in der Testphase imitieren.

Anwendung Szenario
Zum Anzeigen wie testbare Anwendung entwerfen, werde ich eine einfache Anwendungsszenario verwenden, in der Liste der Kategorien und Produkte auf einer Webseite angezeigt werden. Die Anwendung umfasst auch einfache Bildschirme anzeigen, bearbeiten und Erstellen von Kategorien und Produkte. Das einfache Schema in Abbildung 1 gezeigte besteht eine 1: n-Zuordnung zwischen Kategorien und Produkte.
Abbildung 1 Schemas für die Beispielanwendung(Click the image for a larger view)

High-Level Anwendungsentwurf
Wenn Sie eine Anwendung erstellen, kann der ursprüngliche Entwurf eine lange Möglichkeit in Richtung helfen oder hindering langfristige Anwendungszustands wechseln – die Verwaltbarkeit und Testbarkeit. Der Ansatz in vielen Architekturen besteht darin, die optimale Zeitspanne Abstraktion zu finden, ohne viel Aufwand erstellen. Das MVC-Muster stellt bereits einige Architekturrichtlinien definiert drei Ebenen für eine Anwendung bereit. Einige Entwickler können sich vorstellen, dass diese drei Ebenen genügend Abstraktion große Anwendungen bereitstellen. Leider sind, ist häufig nicht der Fall, und wie Sie sehen werden, das Modell problemlos kann mehr als eine einzelne Ebene.
Beachten Sie Sie, dass in vielen kleinen Anwendungen, einschließlich der im Beispiel für diesen Artikel drei Abstraktionsebenen wahrscheinlich ausreichend ist. In diesen Fällen ist einigen Ansichten, Controller und eine Reihe von Klassen für die Interaktion mit wahrscheinlich ausreichend. Allerdings werden mehrere zusätzliche Ebenen der Abstraktion benötigt, um eine hochgradig testbare Anwendung erstellen. Viele Architekturmuster bei unserer Veräußerung können in den Gesamtentwurf, einschließlich das MVC-Muster zu bilden.
XML-Webdienste MVC Code-Behind-Seiten
If you were following ASP.NET MVC before the final release, you may have noticed that the code-behind pages have been removed from the framework. Diese Seiten bedient keine Funktionalität im XML-Webdienste MVC Framework und höher gestuft eingefügt Logik in Ansichten, wobei es nicht gehört. In den Ansichten enthaltenen Logik ist schwierig, im Wesentlichen die gleiche Weise testen, die Code-Behind-Dateien im XML-Webdienste Webformulare schwierig zu testen sind.
Die meisten Lesen dieses Artikels sind vermutlich vertraut, mit dem Muster, aber Sie können nicht über die Auswirkungen für die Anwendung Testbarkeit gedacht haben. Der erste Teil des MVC-Muster ist die Ansicht, die Logik für das Rendering Daten an einen Client enthält. Ursprünglich, dies war eine Benutzeroberfläche, aber ein Client kann einen Webbrowser, einem Webdienst, clientseitige JavaScript und usw. sein. Die Ansicht sollte nur zum Rendern von Daten für ein Consumer verwendet werden, und der Umfang erforderliche Logik sollte in Hilfsklassen so weit wie möglich abstrahiert.
Das Modell stellt Back-End einer Anwendung dar. In diesem Teil der Anwendung, die in der das Muster XML-Webdienste MVC-Implementierung sehr lose definiert ist kann auf vielen verschiedenen Formen, und es sehr wahrscheinlich hängt der Umfang der Anwendung. In einer kleinen Anwendung mit wenig komplexe Geschäftslogik beispielsweise, diese Schicht einfach sein eine Reihe von Business-Objekten mithilfe des Musters Active aufzeichnen, die Sie direkt in der Domänencontroller-Schicht mit interagieren.
Der Controller koordiniert den Ablauf der Anwendung durch Daten aus dem Modell und Übergabe an die gewünschte Ansicht. Da diese Klasse von der Anzeige Logik getrennt ist, sollte mithilfe von ein paar Techniken Sie möglicherweise zum Instanziieren dieser Klasse in einem Komponententest und weisen keine Abhängigkeiten von der ASP.NET-Laufzeit. Dies ermöglicht das vollständige Testen des Domänencontrollers ohne in einem Webserver ausgeführt.

Abstraktion des Modells
Beim Betrachten diese Schichten möglicherweise ein paar Orte angezeigt werden, wobei zusätzliche Abstraktionsschicht Testen eine Anwendung erleichtern würde. Dies ist häufig bei komplexeren Anwendungen, einem Bereich zwischen den Controller und das Modell. Wenn Sie die Anordnung weiter oben beschriebenen haben, könnte Ihre Domänencontroller Aktionen wie folgt aussehen:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formCollection)
{
    Category category = Category.FindById(id);
    category.Name = formCollection["name"];
    category.Save();            
    return RedirectToAction("List");
}
Hier die Herausforderungen sind mindestens hat zwei. Sie können nicht zunächst einfach dieser Code testen, ohne eine Datenbankverbindung. Da wir Active Datensatz Muster verwenden, ist unsere Datenzugriffs-Code für die Entität Domäne gekoppelt, damit wir ihn einfach austauschen kann nicht. Auch wenn Sie Geschäftslogik, die nicht vollständig in der Domäne Entität enthalten ist verfügen, konnte Sie leaking Geschäfts- oder Anwendungslogik in die Controllerklasse beginnen. Dies kann zu Duplizierung von Code oder sogar schlechter führen – Fehler Wortstamm von Inkonsistenzen bei der Implementierung. Dies kann Testbarkeit beeinträchtigen, da Sie jetzt sicher, dass Sie das gleiche Verhalten an mehreren Stellen innerhalb der Anwendung testen müssen.

Das Repository-Muster
Nächste Schritt in Verbessern der Testbarkeit dieses Entwurfs besteht darin, die Repository-Musters nutzen, die von Martin Fowler in seinem Buch Muster von Enterprise Application Architecture beschrieben wird. Repository-Musters ist häufig falsch interpretiert, als Ebene, die zwischen der Anwendung und einer Datenbank befindet, aber es ist tatsächlich eine Schicht, die zwischen der Anwendung und jede Art von permanenten Speicher befindet. Dieses Muster beschränkt die Datenzugriff auf ein Paar Schlüssel stellen, so dass Sie es testen Anwendungslogik in Isolation vereinfachen entfernen.
Implementierungen von Repository-Musters eine Ansicht von Ihren permanenten Speicher, die ein Repository im Speicher werden bereitstellen. Die Schnittstelle zu einem solchen Repository konnte ähnlich sein:
public interface ICategoryRepository
{
    IEnumerable<Category> FindAll();
    void Insert(Category category);
    Category FindById(int id);
    void Update(Category category);
}
Tests in Isolation
Some people might think that testing in isolation is not important and choose to only write integration tests. Testframework wie MSTest oder NUnit kann Integrationstests weiterhin ausgeführt, aber Sie mehreren Ebenen der Anwendung, einschließlich Aufrufe über das Netzwerk den Zugriff auf den Datenträger beibehalten von Daten an eine Datenbank testen und usw.. Diese Arten von Tests sind sehr wichtig und müssen in allen Anwendungen vorhanden sein, aber eine Anwendung wächst, diese Tests werden sehr langsam und können auf verschiedene Entwickler Computern zerbrechliche. Trennen von Komponententests und Integrationstests können Sie die Komponententests schnell und zuverlässig auf Entwickler Computern ausführen während der Ausführung der Integrationstests regelmäßig auf dem Server erstellen.
Wenn Sie beginnen, Klassen von einem anderen und Test in Isolation zu trennen, werden Sie feststellen, dass Tests wird viel weniger zerbrechliche und viel einfacher. Testen Sie die Rand Fällen der aufrufenden Klasse simulieren Sie eine Abhängigkeit, Sie steuern, genau, was er zurückgibt und Sie können leichter. Da sich auf mehreren Ebenen von Abhängigkeiten verlassen nicht sind, müssen Sie auch nicht so viel berücksichtigt beim Vorbereiten der Testszenarios für Ihre. Nur Bedenken Sie, dass Klassen in Isolation kein Test ist Ersatz für das Schreiben von Integrationstests, um sicherzustellen, dass alle Komponenten gut zusammenarbeiten.
Hier zu beachten ist es wichtig, dass verschieben Datenpersistenz außerhalb der Domäne Entität und in einer separaten Klasse, die Sie während der Tests leichter ersetzen können. Anwenden des Musters, der zuvor gezeigten Aktion bearbeiten sieht wie Abbildung 2 aus.

Das Repository einfügen
Sieht der Code im Abbildung 2 sauberen, aber wie ersetzen Sie die Repository-Klasse während der Tests? Ganz einfach. Anstelle der Instanziierung der Klasse innerhalb der Methode, übergeben Sie es in der Klasse. In diesem Fall können Sie es als Konstruktorparameter übergeben. Das Problem ist jedoch, dass Sie die Instanziierung der Controller Klassen steuern nicht. Um dieses Problem zu beheben, können Sie das Dependency Injection-Framework, das ich oben verwiesen. Zahlreiche Frameworks sind auf dem Markt verfügbar. Ich hatte Ninject 2 verwenden. Finden (Wenn Sie mit dem Dependency Injection vertraut sind, Sie im Artikel in der Ausgabe vom September 2005 des MSDN Magazin durch Greif Caprio, "Abhängigkeitsinjektion." Wie bereits erwähnt, ist die erste Schritt unter Dependency Injection die CategoryRepository der Controller über den Controller-Konstruktor übergeben:
public CategoryController(ICategoryRepository categoryRepository)
{
    this.categoryRespository = categoryRepository;
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formCollection)
{
    var categoryRepository = new CategoryRepository();
    Category category = categoryRepository.FindById(id);
    category.Name = formCollection["name"];
    categoryRepository.Update(category);

    return RedirectToAction("List");
}
Als Nächstes müssen Sie Ninject informieren, wenn es eine ICategoryService-Schnittstelle erkennt, eine Instanz der CategoryService einfügen muss. Jeder Dependency Injection-Framework verfügt über eine andere Methode der Konfiguration und Ninject eine codebasierten Konfiguration statt mehr üblichen XML verwendet. Da diese Bindung sehr einfach ist, ist der Code für die Implementierung nur eine Zeile:
Bind<ICategoryRepository>().To<CategoryRepository>().InTransientScope();
Diese Anweisung teilt das Dependency Injection-Framework ICategoryRepository an CategoryRepository in einem flüchtigen Bereich gebunden, d., jedes Mal ein ICategoryRepository angefordert wird h., Sie erhalten eine neue Instanz der CategoryRepository. Dies steht im Gegensatz zu etwa einen Singleton Bereich, in denen Sie weiterhin dieselbe Instanz im Repository wieder erhalten.
Mit dem Controller umgestaltet werden, sodass das Repository in durch den Konstruktor übergeben wird können Sie das Verhalten von dem Controller unabhängig von der Datenbank jetzt testen. Eine Methode zu diesem Zweck ist eine dummy Klasse (häufig als einen Stub bezeichnet), die die ICategoryRepository-Schnittstelle implementiert schreiben. Sie können dann die Methoden implementieren, die müssen Sie dummy Daten zurückgeben und dummy Operationen, obwohl dies wie viel Arbeit scheint. Was geschieht zusätzlich, wenn Sie mehr als ein Satz von unechten Daten für eine einzelne Methode in verschiedenen Tests zurückgeben? Mit mehrere Implementierungen der Stub oder Flags in der Stub landen würde, und, die möglicherweise eine beträchtliche Menge zusätzlichen Aufwand.
Hier ist ein gutes mocking Framework kommen kann. Ein mocking Framework ermöglicht Ihnen das mock-Objekte erstellen. Pseudoobjekte ermöglichen Entwicklern, emuliert das Verhalten von einer anderen Klasse während der Tests über Schnittstellen oder virtuelle Methoden und überprüfen Sie, dass die erwartete Ereignisse aufgetreten ist. Das Verhalten von Mockobjekten (auch einfach als Mocks bezeichnet) ist was ermöglicht das Ersetzen von Abhängigkeiten in den Klassen und Testen Sie diese Klassen ohne die echten Abhängigkeiten an Stelle.
Dies ist möglicherweise ein wenig verwirrend, aber Sie benötigen das mocking Framework einige stubbing führen können. Ja, diese Rechte ist. Da ein Mock ein Objekt, die bestätigen ist, dass eine bestimmte Aktion auf ausgeführt wurde und ein Stub unechten Daten oder Aktionen bietet ermöglicht, sollte es klar sein, dass in den meisten realen Szenarios eine Mischung aus beiden soll. Erhalten diese Tatsache, verfügen die meisten Mockframeworks Funktionen, die Ihnen ermöglicht, Methoden oder Eigenschaften stub ohne Assert Verhalten. (Wenn Sie noch ein wenig unsicher Mocks gegenüber Stubs sind, Martin Fowler einen guten Artikel besitzt, auf dem Thema "Mocks werden Stubs nicht."
Der Code im Abbildung 3 zeigt einen Test, die die ICategoryRepository-Schnittstelle mocks Stub die FindAll-Methode, sodass es eine dummy Liste von Kategorien gibt, und dann die Controllerklasse mocked Instanz übergibt. Die Liste-Methode für die Controllerklasse wird aufgerufen, und das Ergebnis kann bestätigt werden. In diesem Fall wird der Test Assert, dass das Modell ein IEnumerable von Kategorien und, dass in der Liste eine Kategorie ist.
[Fact]
public void ListActionReturnsListOfCategories()
{
    // Arrange

    // create the mock
    var mockRepository = new Mock<ICategoryRepository>();

    // create a list of categories to return
    var categories = new[] { new Category {Id = 1, Name = "test"} };

    // tell the mock that when FindAll is called,
    // return the list of categories
    mockRepository.Setup(cr => cr.FindAll()).Returns(categories);

    // pass the mocked instance, not the mock itself, to the category
    // controller using the Object property
    var controller = new CategoryController(mockRepository.Object);

    // Act
    var result = (ViewResult) controller.List();

    // Assert
    var listCategories = Assert.IsAssignableFrom<IEnumerable<Category>>(result.ViewData.Model);
    Assert.Equal(1, listCategories.Count());            
}
Zu diesem Zeitpunkt ist die Anwendung gute suchen. Wir haben ein Repository, die während der Tests und eine Methode zum Imitieren dieses Repository, sodass wir das Controller-Verhalten isoliert testen können ersetzt werden kann. Für einige Anwendungen möglicherweise alle in Bezug auf den Domänencontrollern erforderlich ist, aber viele Mittel zu großen Anwendungen weiterhin eine weitere Abstraktionsebene Stelle.

Abgestufte Services
In einer komplexeren Anwendung können Sie eine besser abgestimmte Dienst Ansatz und eine Reihe von Aufgabe oder Ereignis Klassen, die die Aktionen darstellen, die im System ausgeführt werden können. Dies kann eine hat zwei Auswirkungen haben. Es kann zunächst die Größe und Komplexität Ihrer Dienstklassen reduzieren. Diese Klassen können zunehmen, sehr groß, wenn Sie nicht enthalten sind. Zweitens bietet es Sie mehr Kontrolle über Abhängigkeiten da jetzt nur die Abhängigkeiten der einzelnen Aktionen anstatt die Abhängigkeiten der Ihre gesamten Dienst kümmern müssen.
Implementieren von Service-Layer
Eine Dienstschicht befindet sich eine Anwendung Domänenmodell und bietet eine Reihe von Operationen, die gegen ausgeführt werden können. Dadurch können Sie einen Ort zum Zentralisieren der Logik, die in Ihrer Anwendung gehört, aber nicht unbedingt in das Domänenmodell gehören möglicherweise – Logik, die sonst wahrscheinlich in den Controller Methoden Verlust haben. Beispielsweise musste was passiert, wenn Sie eine Sicherheitsüberprüfung, um den Code im Abbildung 2? Sie möchten Ausführen des Vorgangs in der Aktion-Methode (Obwohl möglicherweise unter bestimmten Umständen) weil Wiederverwendung dieser Aktion an einem anderen Ort Sie schalten die Sicherheit zusammen mit dem erfordern würde. Oder, was geschieht, wenn Sie eine Buchung um einen Vorgang einfügen möchten? Sie möchten sicherlich nicht dieser Logik innerhalb der Controllerklasse.
Geben Sie stattdessen eine Schicht von Klassen, die eine Anwendung Dienste definieren und diese Klassen verwenden, um verschiedene Aktionen auszuführen. In einer einfachen Anwendung wie das Beispiel in diesem Artikel möglicherweise die Dienstschicht die Methoden für das Repository eng imitieren, können Sie im folgenden Code, aber wenn weitere Geschäftslogik die Anwendung dieser Methoden wahrscheinlich geschäftliche Aktionen anstatt durch grundlegende CRUD-Methoden darstellen würde:
public interface ICategoryService
{
    IEnumerable<Category> FindAll();
    Category FindById(int id);
    void Save(Category category);
}
Für die ersten beiden Methoden im Dienst Aufrufe werden im Repository delegiert und dann die Ergebnisse zurückgegeben werden, aber Sie vielleicht bemerkt, dass die Dienste eine Save-Methode stattdessen die Aktualisierungs- und Methoden für das Repository verfügen. Seit die Anwendung kann um zu ermitteln, ob ein Objekt wurde gespeichert, die Entscheidung, Update aufrufen oder Einfügen auf das Repository bis zum Dienst belassen werden.
Einfügen der Dienstschicht in der Mischung erheblich verringert die Kopplung zwischen den Klassen Controller und die Repository-Klassen während Ihre Domänencontroller heller beibehalten, da viel Logik in den Diensten übertragen werden können. Der Entwurf der Controller-Klassen muss für die Verwendung von Diensten anstelle des Repositorys, entsprechend geändert werden so anstelle von Repositorys in diesen Klassen einfügen, die Dienste einzuschleusen. Wiederum fügen Sie in den Diensten des Repositorys. Diese kompliziert, aber mit Dependency Injection sound könnte, Sie wird nicht selbst feststellen – alles für Sie verkabelte ruft.
Die gesamte Abhängigkeit Injection-Konfiguration (einschließlich der Produkt-Repository und der Dienst) sieht wie Abbildung 4.
public class DefaultModule: NinjectModule
{
    public override void Load()
    {
        Bind<ICategoryService>().To<CategoryService>().InTransientScope();
        Bind<ICategoryRepository>().To<CategoryRepository>().InTransientScope();

        Bind<IProductService>().To<ProductService>().InTransientScope();
        Bind<IProductRepository>().To<ProductRepository>().InTransientScope();
    }
}
Nun, dass wir Repositorys und Dienste über Konstruktoren übergeben, müssen Sie die Domänencontroller verwenden des Dependency Injection-Frameworks zu erstellen. Das Dependency Injection-Framework übernimmt, Konstruktion und Injektion von Abhängigkeiten, jedoch müssen Sie die Controllerklasse vom Framework abgefragt. Dies ist viel einfacher als Sie erwarten können, da XML-Webdienste MVC-Teams Controller Factorys austauschbare vorgenommen. Einfach müssen Sie eine Controller Factory wie in Abbildung 5 implementieren, und Sie können leicht ersetzen die Instanziierung standardmäßig die Controller-Klassen.
public class NinjectControllerFactory : DefaultControllerFactory
{
    private readonly IKernel kernel;

    public NinjectControllerFactory(IKernel kernel)
    {
        this.kernel = kernel;
    }

    protected override IController GetControllerInstance(Type controllerType)
    {
        return (IController)kernel.Get(controllerType);
    }
}
Da die Controller Factory von der Standardimplementierung erbt, müssen Sie nur die GetControllerInstance-Methode überschreiben und fordern den Typ von der Ninject-Kernel, der die Klasse darstellt, die Objektinstanziierung in Ninject, gesteuert. Wenn der Kernel ein Domänencontroller empfängt, self-binds Ninject (versucht, zu erstellen) den Typ da es einen konkreten Typ handelt. Wenn der Kernel ein CategoryController erhält, haben der Konstruktor einen Parameter des Typs ICategoryService. Ninject prüft, wenn Sie eine Bindung für diesen Typ enthält, und wenn den Typ gefunden wird, führt die gleichen Aktion einen Konstruktor gesucht. Der Kernel Ninject instanziiert ein CategoryRepository und übergibt es an den Konstruktor für ControllerService. Übergibt Sie dann das Objekt ControllerService an den Konstruktor für die CategoryController. All, die dies innerhalb des Containers Dependency Injection geschieht einfach durch, die den Typ.
Die Controller Factory muss für die Arbeit registriert werden. Registrierung erfordert das Hinzufügen von nur einer einzigen Zeile in der global.asax-Datei:
public void Application_Start()
{
    // get the Ninject kernel for resolving dependencies
    kernel = CreateKernel();

    // set controller factory for instantiating controller and injecting dependencies
    ControllerBuilder.Current.SetControllerFactory(new NinjectController Factory(kernel));
}
Jetzt, wenn der Benutzer anfordert, eine URL und XML-Webdienste MVC versucht, einen Domänencontroller zu erstellen, endet Sie tatsächlich vom Dependency Injection-Container erstellt wird. Wenn der Domänencontroller getestet wird, können Sie den Dienst zum Testen des Controllers in Isolation, simulieren, wie in Abbildung 6 dargestellt.
[Fact]
public void ListActionReturnsListOfCategories()
{
    // Arrange
    var mockService = new Mock<ICategoryService>();
    var categories = new[] { new Category { Id = 1, Name = "test" } };
    mockService.Setup(cr => cr.FindAll()).Returns(categories);
    var controller = new CategoryController(mockService.Object);

    // Act
    var result = (ViewResult) controller.List();

    // Assert
    var listCategories = Assert.IsAssignableFrom<IEnumerable<Category>>(result.ViewData.Model);            
    Assert.Equal(1, listCategories.Count());
}

Logische Ansicht isolieren
Sie können an diesem Punkt vorstellen, dass Sie genügend Ebenen damit für vollständige Testbarkeit verfügen. Allerdings haben wir die Komplexität der Ansichten nicht noch untersucht. Genommen Sie an, ein Szenario, in denen Sie eine Produkt-Klasse mit einem Preis-Attribut haben. (Die Beispielanwendung zeigt dies.) Nachdem wir sagen, die Sie in diesem Preis für den Benutzer angezeigt werden soll als Währung formatiert. Einige Entwickler möglicherweise sagen Sie diese Anforderung in die Ansicht einfügen sollten, wie hier gezeigt:
<%= Html.Encode(String.Format("{0:c}", this.Model.Price)) %>
Ich gefällt nicht persönlich dieser Ansatz, da ich problemlos nicht Komponententests für diese Logik durchführen kann Wenn das Feld in der Ansicht formatiert. (Ich nicht über eine Benutzeroberfläche testen Framework mind, aber möglichst meine Tests wie möglich in Meine Komponententests ausgeführt werden soll.) Eine Lösung ist eine Eigenschaft der Produkt-Klasse auf den Preis für Anzeige formatieren hinzufügen, aber ich nicht gerne ausführen, da es bedeutet, dass die Aspekte der meine Ansicht Speicherverluste in der Domäne Schicht gestartet werden. Etwas so einfaches als Preis Format möglicherweise nicht wie ein großes Problem erscheinen, aber Probleme scheint immer zu erhöhen, wie Ihre Anwendung die Größe wächst und kleine Elemente Starten Probleme verursacht. Was geschieht beispielsweise, müssen Sie einen Preis anders auf zwei Seiten angezeigt? Starten Sie Ihre Domäne-Objekt Überladungen für jede andere Version hinzufügen?
Ein guter Ansatz ist die Verwendung der Präsentation Modell Muster. Präsentation Modelle zu und von Domänenobjekten zugeordnet werden und Ansicht-spezifische Logik und Werte, die leicht getestet werden können halten können. In der Beispielanwendung wurde ein Präsentationsmodell für jedes Formular erstellt. Da die meisten Formulare sehr ähnlich sind, erben Sie von freigegebenen Basisklassen. In einer komplexeren Anwendung würde jedoch diese wahrscheinlich sehr unterschiedlichen Codepages und Logik je nach auf dem Formular enthalten.
Wenn ein Präsentationsmodell eingesetzt wurden, würde die vorherige Anweisung stattdessen wie folgt aussehen:
<%= Html.Encode(this.Model.DisplayPrice) %>
Sie konnte diese-Eigenschaft problemlos in einem Komponententest wie in Abbildung 7 testen. Beachten Sie, dass die DisplayPrice auf die abstrakte Basisklasse so einen Stub platziert wurde wurde erstellt, die von dieser Basisklasse zur Erleichterung der Tests erbt. Beachten Sie, ein Stub nur ein Objekt, das für Testzwecke verwendet gefälschte Daten zurückgibt. Seit nicht abstrakte Klasse instanziiert werden kann, verwenden Sie den Stub, um die grundlegende Funktionalität zu testen.
[Fact]
public void PriceIsFormattedAsCurrency()
{
    // Arrange
    var product = new Product {Price = 9.22m};
    var presentationProduct = new 
        PresentationProductStub(product);

    // Act
    string cost = presentationProduct.DisplayPrice;

    // Assert
    Assert.Equal("$9.22", cost);
}
Die PresentationProductStub-Klasse wird von der PresentationProductBase-Klasse, die zum und vom Product-Klasse zugeordnet. Dadurch wird die Liste Aktion die ProductController-Klasse wie folgt aussehen:
public ActionResult List()
{
    var products = productService.FindAll();
    return View(new ProductList().MapList(products));
}
Die Produkte werden normalerweise vom Dienst abgerufen wird, jedoch bevor Sie die Ansicht gesendet werden, Sie werden übergeben an eine Methode für die ProductList-Klasse, die die Liste der Produkt-Klassen in eine Liste der ProductList Klassen konvertiert. Verwenden ein Modell für Ihre Ansichten hat mehrere unterschiedliche Vorteile. Zunächst wie Sie bereits gesehen haben, können Sie anzeigen-spezifische Verhalten zu Ihrer Klasse hinzufügen, die problemlos Komponententest unterzogen werden können. Dies ist nützlich für die Formatierung oder Transformieren von Daten für anzuzeigen. Zweitens können Sie verwenden die standardmäßige Modell Sammelmappen XML-Webdienste MVC integriert und platzieren Ihr Präsentationsmodell auf der Parameterliste einer POST-Methode, wie hier gezeigt:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ProductCreate productCreate)
{            
    productService.Save(productCreate.GetProduct());
    return RedirectToAction("List");            
}
Wenn Sie nicht bewusst der Standardbinder Modell XML-Webdienste MVC Funktionsweise sind, ordnet er einfach Felder im HTML-Code nach Namen auf die Eigenschaften der Klasse, die in der Liste der Parameter sind. Wenn ich die Produktklasse in der Parameterliste im vorstehenden Code eingefügt, konnte ein Benutzer böswillig ein Datenelement mit dem Namen "Id" POST.und überschreiben Sie die Taste auf das Modell. Dies ist offensichtlich keine gute Sache. Ein Benutzer konnte entsprechend die Namen eines beliebigen Feldes auf Meine Klasse übereinstimmen und die Werte überschreiben. Verwendung der Modelle in den Ansichten können Sie die Standard-Modell Sammelmappen verwenden (Wenn Sie auswählen) während Sie weiterhin steuern, welche Eigenschaften auf das Domänenobjekt zugeordnet sind.
Die Alternative ist Zuordnung Klassen zu definieren, die Sie zum Zuordnen von Entitäten eingehen über Aktion-Methode Parameter für Entitäten, die Sie in der Datenbank speichern möchten, verwenden können. Dadurch können Sie steuern welche Eigenschaften vor dem Speichern der Datenbank kopieren. Eine andere Option besteht darin, eine Reflektion-basierte Zuordnung Tool konnte Zuordnung der Felder die gewünschten ähnelt der erstellter Jimmy Bogard AutoMapper Tool zu erstellen.

Testen von Routen
Jetzt mit vielen der allgemeinen Architektur Tipps zum Testen aus dem Weg, wir konzentrieren besser abgestimmte testen Ansätze. Die erste Schritt implementieren eine ASP.NET MVC-Anwendung besteht darin, Tests zu überprüfen, das Verhalten der Routen innerhalb der Anwendung zu schreiben. Unbedingt sicherstellen, dass die base Route "~ /"wird an die entsprechenden Domänencontroller und Aktion weiterleiten und von anderen Domänencontrollern und Aktionen weiterleiten korrekt sowie. Es ist diese Tests an Stelle von Anfang verfügen, damit später, Ihre Anwendung mehrere Routen hinzugefügt werden, Sie garantiert, dass Routen, die bereits definiert wurden nicht fehlerhaft sind äußerst wichtig.
Zunächst definieren den Test für den standardmäßigen weiterleiten (siehe acht). In dieser Anwendung ist der Standard-Controller als "Kategorie" definiert.Domänencontroller und die Standard-Aktion in dieser Controller wird "Liste" festgelegt. Unsere Tests base Route überprüfen und bestätigen, dass die richtigen Werte in den Arbeitsplan-Daten enthalten sind.
[Fact]
public void DefaultUrlRoutesToCategoryControllerAndListAction()
{
    // Arrange
    var context = ContextHelper.GetMockHttpContext("~/");
    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);

    // Act
    RouteData routeData = routes.GetRouteData(context.Object);

    // Assert
    RouteTestingHelper.AssertRouteData(routeData, "Category", "List", "");
}
Dieser Test ist eine Hilfsfunktion verwenden, um die Werte für die Standardroute, die hier gezeigte assert:
public static void AssertRouteData(RouteData routeData, 
    string controller, string action, string id)
{
    Assert.NotNull(routeData);
    Assert.Equal(controller, routeData.Values["controller"]);
    Assert.Equal(action, routeData.Values["action"]);
    Assert.Equal(id, routeData.Values["id"]);
}
Diese dieselbe allgemeine Testmuster können Sie alle Routen, der auf das Domänencontroller, Aktion/ID-Muster (z. B. "~/Product/Edit/12"), an der HttpContext folgt übergeben und assert die Werte (siehe Abbildung 9).
[Fact]
public void ProductEditUrlRoutesToProductControllerAndListAction()
{
    // Arrange
    var context = ContextHelper.GetMockHttpContext("~/Product/Edit/12");
    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);

    // Act
    RouteData routeData = routes.GetRouteData(context.Object);

    // Assert
    RouteTestingHelper.AssertRouteData(routeData, "Product", "Edit", "12");
}
Mit dieser Methode die Tests für Routen sind etwas ausführlicher (für Übersichtlichkeit der Learning), und Sie sicherlich gekürzt. Ben Scheirman hat einige interessante Arbeit, die in diesem Bereich erledigt, und er hat eine gute Post in seinem Blog zum Vornehmen von sehr knappe Route Assertionen ("Fluent Route testen in XML-Webdienste MVC").

Abhängigkeiten von Aktionen als Parameter übergeben
Der zweite Tipp ist, alle Abhängigkeiten auf Domänencontroller Aktionen als Parameter an die Aktion-Methode übergeben. Ich verdeutlichen dies mit dem Datei-Download-Beispiel in der Beispielanwendung. Wie bereits früher eine HttpRequest in erwähnt die XML-Webdienste Runtime ist eine statische Klasse, die ist unglaublich schwierig zu ersetzen oder bei Komponententests simulieren. In MVC XML-Webdienste Wrapper-Klassen zur mocked werden diese Klassen ermöglichen, aber der Prozess der imitieren Sie oder stubbing diese kann weiterhin komplex sein.
Ein HttpRequestBase-Objekt simulieren, müssen Sie das HttpContextBase-Objekt, das es befindet sich in simulieren und füllen dann verschiedene Eigenschaften für jede. Für das Problem Speichern von Dateien müssen Sie die HttpServerUtilityBase zu simulieren, die sich auf HttpContextBase befindet. Anstatt mehrere mock-Objekte nachahmen möchten die unterschiedlichen Datenelemente der HttpContextBase erstellen, ist es wäre schön, stellen nur einen Parameter der Methode Aktion die HttpServerUtilityBase und dann in die einzelnen mocked Klasse während der Tests zu übergeben:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, ViewProduct viewProduct, 
    HttpServerUtilityBase server, HttpPostedFileBase imageFile)
{
Feststellen Sie, dass der dritte Parameter im vorhergehenden Code den Typ, den wir verwenden müssen und der Typ, den wir müssen simulieren. Mit der Methodensignatur aussehen müssen wir nur die HttpServerUtilityBase-Klasse zu simulieren. Wenn wir diese Klasse über this.HttpContext.Server zugegriffen wurde, wäre es zum Simulieren der HttpContextBase-Klasse gewesen. Das Problem ist, dass nur hinzufügen die HttpServerUtilityBase-Klasse um die Methodenparameter arbeiten; erleichtern wird nichtSie müssen eine Möglichkeit, XML-Webdienste MVC, wie diese Klasse instanziieren können. Dies ist das Framework Modell Sammelmappen kommen. Beachten Sie, dass HttpPostedFileBase bereits einen benutzerdefinierten Modell Binder standardmäßig zugewiesen hat.
Modell Sammelmappen sind Klassen, die die IModelBinder-Schnittstelle implementieren und bieten eine Möglichkeit XML-Webdienste MVC Typen auf Aktion Methoden zu instanziieren. In diesem Fall benötigen wir eine Sammelmappe Modell für die HttpServerUtilityBase. Wir können eine Klasse erstellen, die wie folgt aussieht:
public class HttpServerModelBinder: IModelBinder
{
    public object BindModel(ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        return controllerContext.HttpContext.Server;
    }
}
Der Binder Modell hat Zugriff auf sowohl der Controller-Kontext und einen Bindungskontext. Dieses Modell Binder greift auf den Controller Kontext einfach und gibt die HttpServerUtilityBase-Klasse in der HttpContext-Klasse zurück. Alle bleibt besteht darin, der XML-Webdienste MVC-Runtime mitzuteilen dieser Sammelmappe Modell verwenden Wenn Sie einen Parameter des Typs HttpServerUtilityBase findet. Beachten Sie, dass dieser Code so wie in der Datei Global.asax gesetzt wird:
public void Application_Start()
{
    // assign model binder for passing in the 
    // HttpServerUtilityBase to controller actions
    ModelBinders.Binders.Add(typeof(HttpServerUtilityBase), new HttpServerModelBinder());
}
Ein HttpServerUtilityBase als Parameter an eine Aktion-Methode übergeben wird, ruft die XML-Webdienste MVC-Laufzeit jetzt die BindModel-Methode auf HttpServerModelBinder um eine Instanz der Klasse abzurufen. Es ist sehr einfach Modell Sammelmappen erstellen, und Sie Test Controller-Aktionen viel leichter machen.

Testen mit Aktion Ergebnisse
Eine frühzeitige Beschwerden über XML-Webdienste MVC betrifft die Schwierigkeit der Tests Operationen wie z. B. die Ansicht von Aktion-Methode wiedergegeben wurde. Mussten Sie die Controller-Kontext, eine Ansicht Modul usw. zu simulieren. Es war wirklich mühsam. In der Vorschau 3 hinzugefügt XML-Webdienste MVC-Team das Konzept der Aktion-Ergebnisse, die Klassen, die von der ActionResult-Klasse abgeleitet und eine Aufgabe, die die Aktion führen darstellen. Jetzt in XML-Webdienste MVC jede Aktion-Methode gibt den Typ ActionResult und ein Entwickler kann aus einer Reihe von integrierten Aktion-Ergebnistypen wählen oder erstellen seines eigenen. Dies hilft beim Testen, da Sie können eine Aktion-Methode aufrufen und überprüfen das Ergebnis finden Sie unter Was ist aufgetreten. Werfen Sie das Beispiel für eine ViewResult, die wir wie hier gezeigt durch Aufrufen der Ansicht-Methode der Basisklasse von Domänencontroller erstellen können:
public ActionResult List()
{
    IEnumerable<Category> categories = categoryService.FindAll();
    return View(ViewCategory.MapList(categories));
}
In dieser Aktion-Methode eine Liste von Kategorien wird als Modell zur Ansicht übergeben, aber keine Ansichtsname angegeben wird. Dies bedeutet, dass diese Aktion die Standardansicht mit dem Namen Liste dargestellt wird. Wenn Sie diese Aktion immer eine leere Ansichtsnamen zurück sicherstellen möchten, können Sie einen Test wie Abbildung 10 schreiben.
[Fact]
public void ListActionReturnsViewResultWithDefaultName()
{
    // Arrange
    var mockService = new Mock<ICategoryService>();
    var controller = new CategoryController(mockService.Object);

    // Act
    var result = controller.List();

    // Assert
    var viewResult = Assert.IsType<ViewResult>(result);            
    Assert.Empty(viewResult.ViewName);
}
Dieser Test mocks CategoryService um die Controllerklasse erstellen. Der Test ruft dann die Liste Aktion zum Abrufen des ActionResult-Objekts. Der Test Assertionen schließlich, dass das Ergebnis vom Typ ViewResult ist und der Name der Ansicht leer ist. Es wäre sehr einfach, Testen den Ansichtsnamen gegen einen bekannten Zeichenfolgenwert oder in Fällen, in dem Sie der Rückgabetyp ist etwas etwas komplexer (JSON Format, z. B.), so überprüfen Sie die Data-Eigenschaft auf das Ergebnis, um sicherzustellen, dass er die erwartete Informationen, enthält wie durch einen Aufruf der Aktion-Methode zurückgegeben.

Empfohlene Literatur
  • Agile Principles, Patterns and Practices in c# von Robert C. Martin und Micah Müller (Prentice Hall, 2006)
  • Muster für Enterprise Application Architecture von Martin Fowler (Addison-Wesley Professional, 2002)
  • Microsoft .NET: Entwickeln von Anwendungen für die Enterprise- von Dino Esposito und Andrea Saltarello (Microsoft Press, 2008)
Zusammenfassung
XML-Webdienste MVC-Team hat sehr viel Aufwand investiert, erstellen Sie eine flexible Architektur, die für einfache Tests ermöglicht. Entwickler können jetzt weitere Ihre Systeme einfach aufgrund von Features wie austauschbare Controller Factorys, Aktion Ergebnistypen und Wrapper Testen der XML-Webdienste Kontexttypen. All dies bietet ASP.NET MVC Entwicklern ein guter Ausgangspunkt, aber die Onus ist weiterhin auf Entwickler entwerfen und Erstellen von Anwendungen, die lose gekoppelten und getestet werden. Ich hoffe, hilft in diesem Artikel als Fortschritt Sie unten in diesem Pfad. Wenn Sie interessiert sind, habe ich diese in der Randleiste Empfohlene Literatur aufgeführt.

Justin Etheredge ist eine Microsoft C#-MVP, Autor CodeThinked.com und Gründer von Richmond Software handwerkliches können Group. Er ist leitender Berater bei Dominion Digital in Richmond, Virginia, stellt er Anleitung im Entwerfen und Erstellen von Systemen für alle Formen und Größen bereit.

Page view tracker