Suggerisci traduzione
 
Altri utenti hanno suggerito:

progress indicator
Nessun altro suggerimento.
 Unit test ASP.NET MVC.
Visualizza contenuto:  affiancatoVisualizza contenuto: affiancato
Questo contenuto è stato tradotto mediante un sistema automatico e può essere modificato dai membri della community. Microsoft invita i membri a contribuire al miglioramento della traduzione facendo clic sul collegamento "Modifica" associato a ciascuna delle frasi seguenti.
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.

Tooling
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 verificabili
Creazione di applicazioni ASP.NET MVC testabile
Justin Etheredge

Questo articolo tratta di:
  • ASP.NET MVC
  • Creazione di soluzioni devono poter essere sottoposti a test
  • Inserimento delle dipendenze
Questo articolo utilizza le seguenti tecnologie:
ASP.NET
Si è sentito probabilmente parlare il vecchio detto, "migliore, più veloce, più economico, prelievo qualsiasi due." Se si desidera un efficace e veloce, non verrà essere economici e se si desidera un rapido ed economico, non verrà essere molto utile. Mezzo economico, più veloce e migliori è necessario scrivere altro codice più rapidamente, a destra? Se solo si trattasse di semplice. Imparare a digitare più rapidamente potrebbe soddisfare due o più dei requisiti, ma non è intende rendono che lo sviluppo di qualsiasi migliore. Così come si crea software migliore? Che cosa significa "più"?
"Più" indica che la produzione di software flessibile e gestibile, con un numero ridotto di difetti; software migliore è gestibilità a lungo termine. A tale scopo, una decisione chiave è garantire che i componenti sono accoppiamento ridotto. Accoppiamento software presenta numerosi vantaggi. Uno che consente di evidenziare è che migliora la possibilità di verifica delle soluzioni. Se si scrive il software che è possibile suddividere facilmente in parti di piccole dimensioni, risulta facile eseguire il test. Sembra semplice quando si word, in questo modo, ma la quantità di software in uso oggi è difficile testare o gestire indica che non è così semplice come vorremmo a pensare. Software deve essere correlata a eseguire operazioni utili, ma gli sviluppatori necessitano di strumenti e tecniche per ridurre l'accoppiamento in modo che soluzioni più facile eseguire il test.

Supporto test in ASP.NET MVC
Abbiamo riscontrato di un renaissance in test di strumenti e tecniche. Ovunque che si è trovare Framework di test e strumenti quali simulazione contenitori e dipendenza Framework di inserimento. Non tutti questi strumenti serve per il test, ma alla fine consentono di creare applicazioni che sono molto più verificabili.
Dal punto di vista generale, un'applicazione devono poter essere sottoposti a test è un'applicazione liberamente con sufficiente per consentire le parti indipendenti da sottoporre a test in isolamento. Scrivere un'applicazione che sia verificabile richiede numerose operazioni di progettazione dall'inizio, ma per creare applicazioni verificabili, gli sviluppatori devono inoltre utilizzare gli strumenti e Framework progettati attorno paradigmi che sono facilmente verificabili. Se non si lavora con strumenti che consentono di creare applicazioni verificabili, si consentirà alcuna quantità di una buona progettazione. Fortunatamente, ASP.NET MVC è stato progettato partendo da zero di essere verificabile e sono state apportate diverse scelte di progettazione principali consentono agli sviluppatori di creare applicazioni verificabili.
Alcuni potrebbe dire che il modello di MVC (Model View Controller) è intrinsecamente più verificabile più il modello di pagina controller (il modello utilizzato da Web Form ASP.NET) poiché incoraggia la separazione tra la logica del flusso dell'applicazione e la logica di visualizzazione. Nel modello MVC, la logica che controlla il flusso dell'applicazione si trova all'interno le classi controller, in modo che uno sviluppatore facilmente possibile creare un'istanza ed eseguire questa logica come se fosse qualsiasi altra classe .NET. Questo consente di eseguire facilmente la logica aziendale in un semplice unit test allo stesso modo che dovrebbe verificare qualsiasi altra classe POCO (normale oggetto CLR precedente).
Un'altra scelta del team di sviluppo Microsoft apportate era consentire molte parti di framework per sia collegabile. Tra le parti più importanti vi sono controller factory, controllare la creazione di classi di controller di un'istanza. Consente agli sviluppatori di sostituzione o si estenda controller factory consente di eseguire una logica che può risolvere e inserire le dipendenze dei controller di. Consentire le dipendenze di un controller per essere inserito in fase di esecuzione è la chiave alla creazione di più applicazioni loosely coupled e verificabile.
In ASP.NET tradizionale, uno degli ostacoli sviluppatori disponibili durante i test è il numero di classi statiche utilizzate durante ciascuna richiesta. Il team di ASP.NET MVC decisione di eseguire il wrapping molte delle classi .NET helper statico (ad esempio HttpContext e HttpRequest) in modo che possono essere sostituiti durante i test con uno stub. ASP.NET MVC fornisce numerose astrazioni per consentire agli sviluppatori evitare l'utilizzo di queste classi, ma in posizioni in cui verrà chiesto di utilizzarli, i wrapper semplificano il codice eseguire il test.

Strumentazione
Anche se ASP.NET MVC disponibili che numerosi gli sviluppatori di strumenti necessarie per creare applicazioni verificabili, non è possibile utilizzare su di esso per nella giusta direzione. Invece è necessario progettare applicazioni deliberatamente per il supporto di test e agevolare alcuni strumenti aggiuntivi:
  • Framework unit test. xUnit.NET Brad Wilson e Jim Newkirk. xUnit fornisce un modo per eseguire automatica di unit test. Molte persone utilizzano MSTest, ma molti altri framework test di unità sono disponibili e ti suggeriamo di dare un'occhiata alcuni di essi. È stato scelto xUnit.NET perché è semplice e facilmente estesa e presenta una sintassi molto parziale. Si sta utilizzando l'utilità di esecuzione test xUnit per resharper, ma l'utilità di esecuzione test xUnit GUI è nella cartella strumenti dell'applicazione di esempio.
  • Framework di inserimento di dipendenza. Ninject 2 da Nate Kohari. Ninject fornisce un modo per associare le classi di applicazione. Descriverò questo approccio in dettaglio più avanti in questo articolo.
  • Simulazioni Framework. Moq da Clarius Consulting . Moq fornisce un framework per simulazioni di interfacce e classi durante i test.

Scenario di applicazione
Per illustrare come progettare un'applicazione devono poter essere sottoposti a test, si utilizzerà uno scenario semplice applicazione in cui elenchi di categorie e prodotti vengono visualizzati in una pagina Web. L'applicazione include inoltre le schermate semplice per visualizzare, modificare e creare categorie e prodotti. Lo schema semplice, illustrato nella Figura 1 , è costituito da un mapping uno-a-molti tra le categorie e prodotti.
Nella figura 1 schema per l'applicazione di esempio (fare clic l'immagine per ingrandirla)

Progettazione di applicazioni ad alto livello
Quando si crea un'applicazione, il progetto iniziale possibile passare un metodo lungo verso aiutare o ostacolare dello stato a lungo termine dell'applicazione, la gestibilità e testabilità. L'approccio adottato in molte architetture consiste la quantità ottimale di astrazione senza creare eccessivo sovraccarico. Il modello MVC fornisce già indicazioni architetturali definendo i tre livelli per un'applicazione. Alcuni sviluppatori potrebbero pensare che questi tre livelli forniscano sufficienti astrazione per produrre applicazioni di grandi dimensioni. Sfortunatamente, che spesso non è il caso e come si vedrà, il modello può essere facilmente più di un solo livello.
È necessario ricordare che, in molte applicazioni di piccole dimensioni, tra cui l'esempio di questo articolo, è probabilmente sufficiente tre livelli di astrazione. In questi casi, alcune visualizzazioni, controller e un insieme di classi per interagire con è probabilmente sufficiente. Tuttavia, alcuni ulteriori livelli di astrazione sono necessari per creare un'applicazione estremamente verificabile. Numerosi modelli di architettura a nostra disposizione agevola la creazione della progettazione complessiva, inclusi il modello MVC.
Pagine code-behind ASP.NET MVC
Se sono stati seguenti ASP.NET MVC prima del rilascio finale, si sarà notato che le pagine code-behind siano state rimosse dal framework. Queste pagine non servite alcuna funzionalità nel framework ASP.NET MVC e promosso inserire logica in visualizzazioni, in cui non appartiene. Qualsiasi logica contenuta nelle visualizzazioni è difficile testare in allo stesso modo i file code-behind in Web Form ASP.NET sono difficili da verificare.
La maggior parte delle è la lettura di questo articolo sono probabilmente familiarità con il modello, ma potrebbe non hanno pensato le relative implicazioni per la testabilità applicazione. La prima parte del modello MVC è la visualizzazione, che contiene la logica per i dati di rendering per un client. In origine, si trattava di un'interfaccia utente, ma un client può essere un browser Web, un servizio Web, lato client JavaScript e così via. La visualizzazione deve essere utilizzata solo per il rendering dei dati per un consumer, e la quantità di logica necessaria deve essere estratti in classi di supporto quanto possibile.
Il modello rappresenta il back-end di un'applicazione. Questa parte dell'applicazione, molto definito nell'implementazione di un criterio di ASP.NET MVC può assumere molte forme diverse, e molto probabilmente dipende l'ambito di applicazione. In un'applicazione piccola con poco logica di business complessi, ad esempio, questo livello potrebbe essere semplicemente un insieme di oggetti business utilizzando il modello Active Record cui interagiscono direttamente nel livello di controller.
Il controller gestisce il flusso dell'applicazione dati dal modello e passando alla visualizzazione appropriata. Poiché questa classe è distinto dalla logica di visualizzazione, utilizzando alcune delle tecniche sarà in grado di creare istanze di questa classe in uno unit test e dipendenze non sono presenti il runtime ASP.NET. In questo modo testing completo del controller di senza dover eseguire all'interno di un server Web.

L'astrazione del modello
Quando si esamina questi livelli, è possibile visualizzare alcune posizioni in cui astrazione aggiuntivo potrebbe rendere più semplice verificare un'applicazione. Questo è comune con le applicazioni più complesse, un'area da tra il controller e il modello. Se si dispone la sovrapposizione descritto in precedenza, le operazioni del controller potrebbe essere simile al seguente:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formCollection)
{
    Category category = Category.FindById(id);
    category.Name = formCollection["name"];
    category.Save();            
    return RedirectToAction("List");
}
Qui le sfide sono almeno duplice. In primo luogo, non è possibile verificare facilmente questo codice senza una connessione di database. Poiché si utilizza il modello di Record Active, il codice di accesso ai dati è associato all'entità di dominio, in modo che è impossibile scambiare facilmente non. Inoltre, se hai la logica aziendale che non è completamente contenuto dell'entità di dominio, è Impossibile avviare perdite aziendali o logica dell'applicazione nella classe controller. Questo potrebbe comportare la duplicazione del codice o anche peggio, bug lo stemming da incoerenze nell'implementazione. Questo può ostacolare la testabilità poiché è necessario assicurarsi che si sta verificando lo stesso comportamento in più posizioni all'interno dell'applicazione.

Il modello di repository
Il passaggio successivo in miglioramento di testabilità di questa struttura è possibile sfruttare il modello di repository, che è dettagliato, Martin Fowler nel suo libro di modelli di architettura delle applicazioni Enterprise. Il modello di repository spesso viene erroneamente interpretato come un livello che si trova tra l'applicazione e un database, ma in realtà è un livello che si trova tra l'applicazione e qualsiasi tipo di archivio permanente. Questo modello limita tutte l'accesso ai dati a poche cifre chiave consente di rimuoverlo per facilitare la logica di test dell'applicazione in isolamento.
Le implementazioni dello schema di repository forniscono una visualizzazione dell'archiviazione permanente che sembra essere un archivio in memoria. L'interfaccia per un repository di tali potrebbe essere simile al seguente:
public interface ICategoryRepository
{
    IEnumerable<Category> FindAll();
    void Insert(Category category);
    Category FindById(int id);
    void Update(Category category);
}
Test in isolamento
alcuni utenti pensi che test in isolamento non è importante e scegliere di scrivere solo i test di integrazione. Un framework di test, ad esempio MSTest o NUnit ancora possibile eseguire i test di integrazione, ma verificare più livelli dell'applicazione, tra cui chiamate attraverso la rete, l'accesso al disco, dati persistenti in un database e così via. Questi tipi di test sono molto importanti e devono essere presenti in tutte le applicazioni, ma quando un'applicazione aumenta, questi test risulti molto lento e possono essere brittle su computer diversi sviluppatori. Separazione di unit test e i test di integrazione consente di eseguire gli unit test in modo rapido e affidabile nei computer di sviluppo durante l'esecuzione regolarmente i test di integrazione del server di generazione.
Quando si inizia separare le classi da un altro e test in isolamento, è possibile trovare che test diventa molto meno brittle e molto più semplice. Simulazione di una dipendenza, è controllare esattamente cosa restituisce e più facilmente, è possibile verificare i casi di bordo della classe chiamata. Inoltre, poiché non affidarsi a più livelli di dipendenze, non è necessario tenere conto di quanto effettuando la preparazione degli scenari di test di. Tenere presente che verifica le classi in isolamento non è una sostituzione per scrivere i test di integrazione per assicurarsi che tutti i componenti funzionino bene insieme.
La cosa importante da notare è la che si sta spostando la persistenza dei dati dall'entità di dominio e inserirle in una classe separata, che è possibile sostituire più facilmente durante i test. Applicare il modello di modifica operazione illustrata in precedenza sarebbe Figura 2 .

Inserire il repository
Il codice nella Figura 2 è parziale, ma come si sostituirà la classe di archivio durante i test? È facile. Invece di creare un'istanza della classe all'interno del metodo, è necessario passarlo nella classe. In questo caso, è possibile passare in come un parametro di costruttore. Il problema, è tuttavia di non controllare la creazione di istanze delle classi controller. Per risolvere il problema, è possibile utilizzare il framework di inserimento di dipendenza fare riferimento a versioni precedenti. Numerosi Framework sono disponibili sul mercato. Ho scelto di utilizzare Ninject 2. (Se non si ha familiarità con l'inserimento delle dipendenze, vedere l'articolo nel numero di settembre 2005 di MSDN Magazine da Caprio Griffin " Inserimento di dipendenza ." Come detto in precedenza, il primo passaggio nell'utilizzo di inserimento delle dipendenze è passare il CategoryRepository il controller tramite il costruttore del controller:
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");
}
Successivamente, è necessario indicare Ninject quando rileva un'interfaccia ICategoryService, è necessario inserire un'istanza del CategoryService. Framework di inserimento ogni dipendenza ha un metodo diverso di configurazione e Ninject utilizza di una configurazione basata sul codice anziché il codice XML più consueto. Poiché questo binding è molto semplice, il codice per implementarlo è sufficiente una riga:
Bind<ICategoryRepository>().To<CategoryRepository>().InTransientScope();
Questa istruzione indica il framework di inserimento di dipendenza per associare ICategoryRepository CategoryRepository in un ambito temporaneo, che significa che ogni volta, viene richiesto un ICategoryRepository è ottenere una nuova istanza della CategoryRepository. Questo è in contrasto con simile a un ambito singleton, in cui si continua a ottenere la stessa istanza del repository.
Con il controller a refactoring in modo che l'archivio venga passato tramite il costruttore, è ora possibile verificare il comportamento del controller in modo indipendente del database. Un modo per eseguire questa operazione è la scrittura di una classe fittizia (comunemente definita come stub), che implementa l'interfaccia ICategoryRepository. È quindi possibile implementare i metodi che è necessario restituire dati fittizi e operazioni fittizio, sebbene ciò sembra molto lavoro. Inoltre, cosa accade quando è necessario restituire più set di dati fittizi per un singolo metodo nei diversi test? Si sarebbe finisce con più implementazioni del stub o flag nello stub e che potrebbe essere una quantità significativa di sforzo aggiuntivo.
Di seguito è dove può essere un framework di simulazione buona. Un framework di simulazione consente di creare oggetti simulati. Oggetti simulati consentono agli sviluppatori di emulare, tramite le interfacce o metodi virtuali, il comportamento di un'altra classe durante i test e verificare che gli eventi previsti si è verificato. Il comportamento di oggetti simulati (denominato anche semplicemente simulazioni) è ciò che consente di sostituire dipendenze nelle classi e quindi verificare le classi senza le dipendenze reali posto.
Questo potrebbe essere un po' di confusione, ma è necessario che il framework di simulazione consentono di eseguire alcune venivano. Risposta corretta. Poiché una simulazione è un oggetto che consente di effettuare un'asserzione che è stata eseguita una determinata azione su di esso e uno stub fornisce dati fittizi o le azioni, dovrebbe essere chiaro che in molti scenari reali si desidera una combinazione dei due. Dato questo fatto, Framework di simulazione più dispongono di funzionalità che consente di stub di metodi o proprietà senza effettuare l'asserzione alcun comportamento. (Se si è ancora un po' chiaro simulazioni e stub, Martin Fowler applicato un buon articolo sull'argomento " Le simulazioni non di stub ."
Il codice nella Figura 3 viene illustrato un test che mocks l'interfaccia ICategoryRepository, il metodo FindAll generato in modo che restituisce un elenco di categorie fittizio e quindi passa l'istanza simulato alla classe controller. Viene chiamato il metodo elenco sulla classe controller e il risultato può essere dichiarato. In questo caso, il test è l'asserzione che il modello è una categoria di IEnumerable e che è una categoria nell'elenco.
[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());            
}
A questo punto, l'applicazione ricerca valida. È necessario un archivio che può essere sostituito durante il test e un metodo per simulazioni questo repository in modo che è possibile verificare il comportamento del controller in isolamento. Per alcune applicazioni, potrebbe essere che è necessario ai controller di, ma il medie per grandi applicazioni ancora un altro livello di astrazione posto.

Servizi specifici
In un'applicazione più complessa, è possibile adottare un approccio più specifico del servizio e dispongono di un insieme di attività o eventi classi che rappresentano le azioni che possono essere eseguite nel sistema. Questo può avere effetti duplice. In primo luogo, è possibile ridurre il dimensioni e la complessità delle classi di servizio. Queste classi possono aumentare molto grandi, se non si trovassero. In secondo luogo, fornisce maggiore controllo sulle dipendenze poiché ora è necessario solo per le dipendenze di singole azioni anziché le dipendenze di tutto il servizio.
Implementazione del livello di servizio
Un livello di servizio posta sopra di modello di dominio di un'applicazione e fornisce un insieme di operazioni che possono essere eseguite su di essa. In questo modo è un punto di centralizzare la logica che appartiene nell'applicazione, ma potrebbe non appartiene necessariamente all'interno il modello di dominio, che sarebbe stato in caso contrario probabilmente perdita nei metodi del controller di logica. Ad esempio, se è necessario effettuare un controllo protezione di tutto il codice nella Figura 2 ? Non si desidera eseguire l'operazione nel metodo azione (anche se è possibile in alcuni casi) perché il riutilizzo di questa azione in un'altra posizione richiede per la protezione con esso. Oppure, se si desidera inserire una transazione intorno a un'operazione? Certamente non desiderate questa logica all'interno della classe controller.
In alternativa, è possibile fornire un livello di classi che definiscono i servizi dell'applicazione e utilizzare tali classi per eseguire azioni diverse. In un'applicazione semplice, come l'esempio in questo articolo, il livello di servizio strettamente potrebbe simulare i metodi l'archivio, come può vedere nel codice seguente, ma se l'applicazione dispone di più regole di business, questi metodi rappresenterebbe probabilmente azioni business anziché metodi CRUD base:
public interface ICategoryService
{
    IEnumerable<Category> FindAll();
    Category FindById(int id);
    void Save(Category category);
}
Per i primi due metodi nel servizio, le chiamate vengono delegate al repository e quindi i risultati vengono restituiti, ma è possibile notare che i servizi disponibile un metodo Save invece i metodi Update e Delete sull'archivio. Poiché l'applicazione è in grado di per determinare se un oggetto è stato salvato, la decisione di chiamare il metodo Update o INSERT sull'archivio può essere lasciata fino al servizio.
Inserimento di notevolmente il livello di servizio nella combinazione riduce l'abbinamento tra le classi controller e le classi di repository mantenendo i controller di più chiari perché è possibile propagare una grande quantità di logica nei servizi. La struttura delle classi controller deve essere modificata in base l'utilizzo di servizi anziché archivi, in modo che invece di inserire gli archivi in queste classi, è possibile inserire i servizi. A sua volta, è possibile inserire il repository nei servizi. Questo potrebbe sembrare complicato, ma con l'inserimento delle dipendenze, è non considerare, si ottiene cablata per è.
La configurazione dell'inserimento dipendenza intera (inclusi il repository del prodotto e servizio) è simile a Nella figura 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();
    }
}
Si sta passando il repository e servizi tramite costruttori, è necessario creare il controller utilizzando il framework di inserimento di dipendenza. Il framework di inserimento di dipendenza gestisce la costruzione e l'inserimento delle dipendenze, ma è necessario richiedere il framework la classe controller. Questo è molto più semplice non è possibile che si prevede di perché il team di ASP.NET MVC il controller factory collegabile. È necessario semplicemente implementare un controller factory come nella Figura 5 e la creazione di predefinito di un'istanza delle classi controller quindi è possibile sostituire facilmente.
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);
    }
}
Poiché il controller factory eredita dall'implementazione predefinita, è sufficiente eseguire l'override del metodo GetControllerInstance e richiedere il tipo dal kernel Ninject, che è la classe che controlla l'istanza di oggetto in Ninject. Quando il kernel riceve un tipo di controller, Ninject self-binds (tenta di costruire) il tipo perché è un tipo concreto. Se il kernel riceve un tipo CategoryController, è necessario che il costruttore avrà un parametro di tipo ICategoryService. Ninject ricerca per verificare se è un'associazione per questo tipo, e quando viene rilevato il tipo, esegue la stessa azione, ricerca di un costruttore. Il kernel Ninject crea un'istanza un CategoryRepository e lo passa al costruttore per ControllerService. Quindi passa l'oggetto ControllerService al costruttore per il CategoryController. Tutto che ciò si verifica all'interno del contenitore di inserimento dipendenza semplicemente mediante la richiesta per il tipo.
Il controller factory deve essere registrato per il lavoro. Registrazione richiede l'aggiunta di una singola riga nel file global.asax:
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));
}
A questo punto, quando l'utente richiede un URL e ASP.NET MVC tenta di creare un controller, effettivamente finisce creato dal contenitore di inserimento di dipendenza. Quando il controller viene verificato, è possibile simulazione il servizio per verificare il controller in isolamento, come illustrato nella Figura 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());
}

Isolare la logica di visualizzazione
A questo punto si potrebbe pensare di avere sufficiente livelli per consentire la testabilità completo. Tuttavia, si dispone di non ancora esplorato la complessità delle visualizzazioni. Si supponga uno scenario in cui avere una classe prodotto con un attributo di prezzo. (L'applicazione di esempio mostra questo). A questo punto si supponga che si desidera visualizzare il prezzo per l'utente formattato come valuta. Alcuni sviluppatori potrebbero essere che è consigliabile posizionare questo requisito nella visualizzazione, come illustrato di seguito:
<%= Html.Encode(String.Format("{0:c}", this.Model.Price)) %>
Personalmente non mi piace questo approccio perché Impossibile eseguire unit test per la logica facilmente quando viene formattato il campo nella visualizzazione. (Non presente utilizzando un'interfaccia utente framework di test, ma si desidera ottenere come parte del mio test minor nei test di unità.) Una soluzione è per aggiungere una proprietà della classe di prodotto per formattare il prezzo per la visualizzazione, ma non mi piace questa operazione poiché significa che i problemi della visualizzazione vengano avviati una perdita nel livello del dominio. Un'operazione semplice come un formato di prezzo potrebbe non sembrare un lavoro di grandi, ma sempre problemi sembrano aumentare l'applicazione aumenta di dimensioni ed elementi piccoli, iniziano a causare problemi. Ad esempio, cosa succede se si desidera visualizzare in modo diverso un prezzo su due pagine? Verrà iniziare ad aggiungere overload per ogni versione diversa per l'oggetto dominio?
Un approccio efficace consiste nell'utilizzare il modello modello di presentazione . Modelli di presentazione possono essere mappati da e verso gli oggetti dominio e possono contenere logica specifica di visualizzazione e valori che possono facilmente essere verificati. Nell'applicazione di esempio è stato creato un modello di presentazione per ogni modulo. Poiché la maggior parte dei moduli sono piuttosto simile, ereditano dalle classi base condivise. In un'applicazione più complessa, tuttavia, sarebbe probabilmente contiene codice molto diverso e la logica in base al modulo.
Se un modello di presentazione sono stato utilizzato, l'istruzione precedente avrà invece il formato seguente:
<%= Html.Encode(this.Model.DisplayPrice) %>
È facilmente possibile verificare questa proprietà in uno unit test come quello illustrato nella Figura 7 . Si è creato nota che il DisplayPrice è stato inserito nella classe base astratta, così uno stub che eredita da questa classe base per facilitare il test. Tenere presente che uno stub è solo un oggetto che restituisce dati finti utilizzati per il testing. Poiché è Impossibile creare un'istanza di una classe astratta, è possibile utilizzare lo stub per verificare la funzionalità di base.
[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);
}
La classe PresentationProductStub deriva dalla classe PresentationProductBase, che associa la da e verso la classe di servizio. In questo modo l'azione di elenco per la classe ProductController simile al seguente:
public ActionResult List()
{
    var products = productService.FindAll();
    return View(new ProductList().MapList(products));
}
I prodotti sono estratta in genere dal servizio, ma prima che vengano inviati alla visualizzazione, sono passati a un metodo nella classe ProductList che converte l'elenco delle classi prodotto in un elenco di classi ProductList. Utilizzando un modello per le visualizzazioni presenta diversi vantaggi di distinti. In primo luogo, come già visto, è possibile aggiungere comportamento specifico della visualizzazione a una classe che può essere facilmente sottoposto a unit test. Questo risulta utile per la formattazione o la trasformazione dei dati per visualizzare. In secondo luogo, è possibile utilizzare i raccoglitori di modello predefinito incorporati in ASP.NET MVC e posizionare il modello di presentazione il parametro di un metodo POST, come illustrato di seguito:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ProductCreate productCreate)
{            
    productService.Save(productCreate.GetProduct());
    return RedirectToAction("List");            
}
Se non si è a conoscenza di come funziona il Raccoglitore di modello predefinito di ASP.NET MVC, associa semplicemente i campi il codice HTML per nome nella proprietà della classe nell'elenco dei parametri. Se inserisce la classe di servizio nell'elenco dei parametri nel codice precedente, un utente potrebbe POST di un blocco di dati denominati "Id" e sovrascrivere la chiave sul modello da utenti malintenzionati. Ovviamente non è una cosa positiva. Allo stesso modo, un utente potrebbe far corrispondere i nomi di un campo la classe e sovrascrivere i valori. Utilizzo di modelli nelle visualizzazioni consente di utilizzare i raccoglitori di modello predefinito, se sceglie, mentre ancora in grado di controllare la cui proprietà vengono mappate all'oggetto dominio.
L'alternativa consiste nel definire classi di mapping che è possibile utilizzare per il mapping entità in entrata tramite parametri di metodo azione alle entità che si desidera salvare nel database. Questo consente di controllare le proprietà da copiare prima di salvare nel database. In alternativa, è possibile creare simile a uno strumento di mapping basata su reflection in cui è possibile utilizzare per mappare i campi che si desidera il Strumento AutoMapper creato da Jimmy Bogard.

Test di route
A questo punto, con molti dei suggerimenti dell'architettura ad alto livello per il testing di è opportuno concentrarsi sulla approcci di test più specifici. Il primo passaggio nell'implementazione di un'applicazione ASP.NET MVC consiste nello scrivere test per verificare il funzionamento delle route all'interno dell'applicazione. È importante assicurarsi che la route di base "~ /" verrà inoltra azione i controller appropriato e che altri controller e azioni correttamente inoltrano nonché. È estremamente importante avere questi test posto dall'inizio, affinché in un secondo momento, le route più vengono aggiunti all'applicazione, si è certi che la route che sono stati già definite non sono interrotte.
Avviare il test per il valore predefinito di definizione ciclo di lavorazione (vedere la Figura 8 ). In questa applicazione, il controller predefinito viene definito il controller di "categoria", e l'azione predefinita di questo controller di è impostato a "elenco". Il test deve verificare la route base e asserire che i valori corretti sono contenuti nei dati del ciclo di lavorazione.
[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", "");
}
Questo test utilizza una funzione di supporto per l'asserzione i valori di ciclo di lavorazione predefinito, illustrato di seguito:
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"]);
}
Utilizzando questo stesso modello generale di test, è possibile passare l'oggetto HttpContext qualsiasi route che segue il modello controller, azione/ID (ad esempio "~/Product/Edit/12") e asserzione i valori (vedere Nella figura 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");
}
Con questo metodo, i test per le route sono un po' dettagliati (per maggiore chiarezza di apprendimento) e sono certamente può essere abbreviate. Ben Scheirman ha effettuato operazioni interessanti in quest'area e ha un buon post sul suo blog su come rendere le asserzioni di route molto conciso " Testing di route abbiano una conoscenza approfondita in ASP.NET MVC ").

Dipendenze di passaggio di azioni come parametri
Il secondo suggerimento consiste nel passare tutte le dipendenze alle azioni controller come parametri al metodo di azione. Illustrato nell'esempio di download di file nell'applicazione di esempio. Come indicato in precedenza, un HttpRequest nel runtime di ASP.NET è una classe statica che è estremamente difficile sostituire o simulazione durante l'esecuzione di unit test. In ASP.NET MVC classi wrapper sono fornite per consentire queste classi per essere esempio, ma il processo di simulazioni li o venivano in può essere ancora complesso.
Per la simulazione di un oggetto HttpRequestBase, è necessario simulazione l'oggetto di HttpContextBase alla relativa nella, quindi compilare diverse proprietà per ciascuna. Per il problema di salvataggio dei file, è necessario simulazione il HttpServerUtilityBase si trova su HttpContextBase. Anziché tentare di creare più oggetti simulati per simulare tutte le diverse parti del HttpContextBase, sarebbe utile verificare il HttpServerUtilityBase un parametro del metodo azione e quindi passare in quella classe simulati singolo durante i test:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, ViewProduct viewProduct, 
    HttpServerUtilityBase server, HttpPostedFileBase imageFile)
{
Si noti che il terzo parametro nel codice precedente è il tipo che è necessario utilizzare e il tipo che è necessario per la simulazione. Con la firma del metodo simile al seguente, è necessario solo simulazione la classe HttpServerUtilityBase. Se si fosse accessibile questa classe tramite this.HttpContext.Server, sarebbe stato necessario simulazione nonché la classe HttpContextBase. Il problema è che solo aggiunta che la classe di HttpServerUtilityBase per i parametri del metodo non farlo funzionare, è necessario fornire un modo per indicare come creare un'istanza di questa classe di ASP.NET MVC. Si tratta in cui i raccoglitori di modello del framework sono disponibili in. Si noti che HttpPostedFileBase disponga già di un Raccoglitore di modello personalizzato per impostazione predefinita assegnato a è.
Modello raccoglitori sono classi che implementano l'interfaccia IModelBinder e consentono di ASP.NET MVC creare un'istanza tipi di metodi di azione. In questo caso, è necessario un Raccoglitore di modello per il HttpServerUtilityBase. È possibile creare una classe che è simile al seguente:
public class HttpServerModelBinder: IModelBinder
{
    public object BindModel(ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        return controllerContext.HttpContext.Server;
    }
}
Il Raccoglitore di modello può accedere a sia di contesto del controller e di un contesto di associazione. Raccoglitore questo modello è sufficiente accede contesto del controller di e restituisce la classe di HttpServerUtilityBase sulla classe HttpContext. Tutto ciò che rimane consiste di indicare il runtime di ASP.NET MVC utilizzare Raccoglitore questo modello quando viene rilevato un parametro di tipo HttpServerUtilityBase. Si noti che questo codice viene inserito nel file Global.asax come così:
public void Application_Start()
{
    // assign model binder for passing in the 
    // HttpServerUtilityBase to controller actions
    ModelBinders.Binders.Add(typeof(HttpServerUtilityBase), new HttpServerModelBinder());
}
A questo punto, quando un HttpServerUtilityBase viene passato come parametro a un metodo di azione, la funzione runtime di ASP.NET MVC richiama il metodo BindModel sul HttpServerModelBinder per ottenere un'istanza di tale classe. È molto semplice creare il modello raccoglitori e rendono molto più semplice operazioni di controller di test.

Test con risultati di azione
Tra le lamentele anticipate su ASP.NET MVC riguardano la difficoltà di operazioni di testing, ad esempio quali visualizzazione è stato eseguito il rendering da un metodo di azione. Era necessario simulazione il contesto di controller, un motore di visualizzazione e così via. È stato molto più complesso. In anteprima 3, il team di ASP.NET MVC aggiunto il concetto di risultati di azione, che sono classi che derivano dalla classe ActionResult e rappresentano un'attività che dovrà eseguire l'azione. A questo punto, in ASP.NET MVC, ogni metodo azione restituito il tipo ActionResult e uno sviluppatore può scegliere tra diversi tipi di azione-risultati predefiniti di o creare il proprio. In questo modo con il test di quanto è possibile richiamare un metodo di azione e controllare il risultato per vedere cosa è accaduto. Si consideri l'esempio di un ViewResult che è possibile creare richiamando il metodo visualizza nella classe controller base, come illustrato di seguito:
public ActionResult List()
{
    IEnumerable<Category> categories = categoryService.FindAll();
    return View(ViewCategory.MapList(categories));
}
In questo metodo azione, un elenco di categorie viene passato come il modello della visualizzazione, ma non viene fornito alcun nome di visualizzazione. Ciò significa che questa azione renderà la visualizzazione predefinita denominata. Se si desidera assicurarsi che questa azione è sempre restituisce un nome di visualizzazione vuota, è possibile scrivere un test come nella figura 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);
}
Questo test mocks il CategoryService per creare la classe controller. Il test richiama quindi l'azione di elenco per ottenere l'oggetto ActionResult. Il test viene infine asserisce che il risultato è di tipo ViewResult e che il nome della visualizzazione è vuoto. Sarebbe molto semplice verificare il nome della visualizzazione con un valore di stringa noti o nei casi in cui il tipo restituito è un'operazione un po' più complesso (JSON formato, ad esempio), per controllare la proprietà di data nel risultato per assicurarsi che contenga informazioni previste restituito da una chiamata al metodo azione.

Si consiglia di lettura
  • Agile Principles, Patterns, and Practices in C# da c. Robert Martin e Martin Micah (Prentice Hall, 2006)
  • Modelli di architettura delle applicazioni Enterprise da Martin Fowler (Addison-Wesley Professional, 2002)
  • Microsoft .NET: Architettura delle applicazioni per le aziende Dino Esposito e Andrea Saltarello (Microsoft Press, 2008)
Ritorno a capo le
Il team di ASP.NET MVC ha investito un notevole impegno alla creazione di un'architettura flessibile che consente di test semplice. Possono gli sviluppatori ora verificare più facilmente i propri sistemi a causa di funzionalità come collegabile controller factory, tipi di risultato azione e wrapper per i tipi di contesto ASP.NET. Tutto questo fornisce ASP.NET MVC agli sviluppatori un buon punto di partenza, ma il onus è ancora presente agli sviluppatori di progettare e costruire applicazioni loosely coupled e verificabile. Spero che questo articolo consente di avanzamento verso il basso il percorso. Se si è interessati, È stato elencato nella barra laterale lettura consigliata.

Justin Etheredge è un Microsoft C# MVP, autore all' CodeThinked.com e fondatore del gruppo Richmond Software abilità. È un consulente senior in Dominion digitale in Richmond, Virginia, in cui ha fornisce indicazioni in Progettazione e sistemi di creazione di tutte le forme e dimensioni.

Page view tracker