Skip navigation

Test-Driven ASP.NET in Visual Studio 2010

Apply TDD Practices in ASP.NET Development

For many years developers in the .NET space didn’t pay much attention to emerging patterns and practices. The deep application of the Rapid Application Development paradigm led to a focus on tools and techniques for faster development, rather than for doing development right. With the release of Visual Studio 2010 Microsoft seems committed to return developers’ attention to the importance of design principles. This is a general point, but it's especially true for Web developers. The integration of ASP.NET MVC 2 in the Visual Studio package and the introduction of editing facilities are signs of a change in direction. As a result, test-driven development (TDD) is now much easier to accomplish for developers willing to do it.

TDD is a methodology that naturally causes you to think about the expected interface and behavior of methods well before you start writing the code. We all know that design is important, and we all wish we had enough time to do it. TDD is an approach that may appear radical at first and it takes time to fully digest. However, its goal is quite simple: to help you quickly write clean code that works. In this article, I’ll review the main steps of a test-driven development session that showcases the new capabilities of Visual Studio 2010.

TDD At a Glance

In the traditional approach to coding, you develop a method based on the features the method must support. You start with a relatively simple body and then increase its capabilities until you reach the original goal. Along the way, you use the debugger to see how things are going, and if data is being moved around correctly. When all is okay—and if you’re a scrupulous developer—you consider writing unit tests to verify the method's behavior and make it easier to catch regression failure later.

At its core, a unit test is a program that uses the public interface of your classes. Unit tests are expected to be quick. Unit tests also need to focus on small parts of code to be effective and minimize hidden dependencies—the most common source of the most weird errors and nasty effects on production code. When you write unit tests you may realize that testing components in isolation may not be that easy. For example, imagine you have a button click handler that runs a query and then processes the results before updating the user interface. If you test the handler as a whole, you actually perform an integration test because you need to have the database connected to the application. To test individual units of code, you need to separate the logic that processes the results from the code that retrieves data. The processing logic can, and should, be tested on any coherent data, but not necessarily data physically retrieved from a database for the purpose of the test.

If you proceed this way, you eventually decide that tests are too hard to write and don’t really give you concrete benefits. It’s your code after all and it works; a test won’t make the code richer and more appealing to users.

This approach has worked for years and still does today. However, as the code becomes more complex the debugger alone is no longer sufficient and needs to be backed by good unit tests. You can write unit tests before you code (as TDD suggests) or as an afterthought. It doesn’t really matter when you do it as long as you develop an exhaustive set of tests. TDD is considered an effective methodology to achieve just this result. Some small changes in the Visual Studio 2010 refactoring tools and the Test project template make TDD worth a try.

Testable Code for Web Forms

Testability is too often only associated with ASP.NET MVC applications, and valid reasons exist for thinking that Web Forms are harder to test than ASP.NET MVC. However, it doesn’t mean you can't apply TDD to Web Forms.

Figure 1 shows how to refactor the typical code of a code-behind class to make it easier to test. Note that this is not the only possible approach and it's a rather simple one. The next step would be to implement the Model-View-Presenter pattern, to gain a cleaner separation between rendering the view and processing logic. 

The code-behind class incorporates an instance of a controller class and invokes methods on the controller instance for any significant interaction. In particular, the page contains a button that processes the contents of a text box and updates a Label control with the results. The Button1_Click handler first packages any input data required for business processing into an ad hoc data structure. Next, it passes a reference to this data structure to the controller instance. This is done via the public Parameters property defined on the controller. The method invoked on the controller does its work and updates output members on the same Parameters data structure. Finally, the handler gets calculated data and refreshes the user interface. Figure 2 shows the source code for the controller class. You should plan to have one controller class and one parameter class for each Web Form.

The controller has no dependencies on the user interface, which is a first good result. Let’s test it. You add a new test project to your solution and add a reference to the Web Forms project. Figure 3 shows a test method from Visual Studio 2010.

Looks great, does it? If you run the test, however, you get an exception: the connection string used by the LINQ-to-SQL object model is not available in the testing environment. At some point you need to test against the database, so adding a proper <connectionStrings> section to the test configuration file is not an issue. In addition, Visual Studio 2010 makes it even easier by letting you manage multiple versions of web.config.

But the name of the test says “TestIfFetchedCustomerIsDisplayedCorrectly”: this is, therefore, the real purpose of the test method. For this to happen, you don’t need to gain access to the database. So you go back to the source code and add an extra layer of code to decouple the controller from the physical data access. Here’s a possible implementation:

public void GetCustomerData()

\\{

    var repo = new CustomerRepository();

    Customer customer = repo.FindById(Parameters.CustomerID);

     if (customer != null)

        Parameters.Customer = customer;

\\}

This code looks nicer but it doesn’t fix the issue entirely. You need a way to force the controller to use a fake repository object during tests that doesn’t require a database connection (see Figure 4.)

By wrapping a data access code to a repository class, and adding a new constructor that accepts an externally created repository object, you make the controller class fully testable. The burden of testing data access has been delegated to the team that actually takes care of the data access layer. More importantly, the code still works and its nicer to read and cleaner, even if it’s plain Web Forms and not that-cool-thing-called-MVC.

A Sample TDD Session 

Let’s see how you could achieve the same results using TDD. Imagine a blank class named WebForm1_Controller with only the default constructor and a Parameters property. At some point, you find out you need a GetCustomerData method to retrieve data for a given customer. Figure 5 shows your starting point.

You know you want a GetCustomerData method that reads input data from parameters and stores there any calculated values. The red squiggle underlining GetCustomerData in Figure 5 indicates that the method doesn’t exist yet, but Visual Studio 2010 now comes with an extended Refactor context menu that offers to create a method stub for you:

public void GetCustomerData()

\\{

   throw new NotImplementedException();

\\}

At this point, you're ready to run the test for the first time; it will fail as expected (and also as recommended by TDD). The failure is due to the exception thrown by the method’s implementation. Next, you move on to Refactor method and typically add some code as shown below:

public void GetCustomerData()

\\{

    Customer customer = new Customer() \\{CustomerID = Parameters.CustomerID\\};

    Parameters.Customer = customer;

\\}

The test now passes, but the method is not fulfilling its goals yet. You extract a method from the preceding assignment, as shown below:

public void GetCustomerData()

\\{

    Customer customer = FindById(Parameters.CustomerID);

    Parameters.Customer = customer;

\\}

private Customer FindById(string id)

\\{

    return new Customer() \\{ CustomerID = id \\};

\\}

The test still passes but now you need to add code to the method that hits the database. As you add data access code to FindById, you receive an exception during the test. The reason is that the test project has no connection string information and this breaks the construction of the LINQ-to-SQL data context object. After another couple of refactoring steps you come up with the code in Figure 4. The test now looks like that shown in Figure 6.

The FakeCustomerRepository class is a test-only class that implements the ICustomerRepository interface and mimics the behavior of the data access layer. You can write that class to return canned values, or you can opt for a mocking framework (e.g., Rhino Mocks, Moq) to return a dynamically configurable object.

Using Discipline

In this article I just scratched the surface of TDD and unit testing in Visual Studio 2010. The nice thing is that by using a disciplined approach you also can achieve the same benefits in Web Forms. In a future article, I’ll say more about testing techniques and mocking frameworks.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish