TDD and the Separation of Concerns


I have seen a lot of posts, primarily in the forums, with people who are stating that TDD does not work for web applications. The typical post is something like this:

So I’m starting a new ASP.NET project and want to proceed with a test-driven
approach as much as possible. I have decided that I will ceratainly NOT use
the ASP.NET MVC framework, and instead go with the traditional web forms
approach.

The first issue I ran into is that, in order to create my unit tests, I’d
need mocks for Application, Session, Context etc. Thinking that through,
these mocks would have to do practically *everything* offered by the
Application, Session etc objects. Suddenly I’m *duplicating* much of what
ASP.NET is – all for the sake of unit testing my code. That seems like a
ridiculous amount of work to get automated unit tests.

Am I missing something? Or is it simply not possible to use TDD for ASP.NET
Web forms application development?

The questions here come from ignorance, both of what ASP.NET should be and what TDD is. To understand this better, let’s look at separation of concerns in a "traditional" (ie, non MVC) ASP.NET page.

Separation of Concerns

The separation of concerns, for this article, is breaking down constituent parts of your application. You have a UI (the ASPX page), some business logic (a business assembly) and data. We all should be familiar with this model, as Microsoft has been pushing n-tier development for ages.

So, you start with an ASPX page. In this case, we will assume it wants to be a blog page. We create the page (blog.aspx) and we end up with code that looks like this (providing, of course, we have not whacked the templates):

using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;

public partial class blog : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }
}

As this is a blog page, we need to fill it in with the blog information, so we alter the Page_Load to read:

    protected void Page_Load(object sender, EventArgs e)
    {
          BindBlogRepeater();
    }

And we create a routine that binds the blog to the Repeater control (the actual contents and how we might display this are inconsequential).

private void BindBlogRepeater()
{
    //First we need some data
    BlogRepeater.DataSource = GetBlogData().Tables[0].DefaultView;
    BlogRepeater.DataBind();
}

Then, we know we need some data so we create a routine to pull the data.

private DataSet GetBlogData()
{
    DataSet ds = new DataSet();
    SqlConnection connection =
                 
new SqlConnection(ConfigurationManager.ConnectionStrings
                                     
["myConnString"].ToString());
    //More code to fetch data here

    return ds;
}

Then, we write a forum post on how this is completely untestable using TDD. To which, I say, "you are absolutely right". But, we also have created a single executable, with UI, business and data tiers smashed together. No wonder why we cannot use TDD.

The problem, if we examine it deeper, is we were looking at the application from the UI end. The same problem exists if we look at the application from the database, BTW. Our storage, UI and application (or applications, if we are using a SOA approach) are different concepts. They should be designed differently.

A Better Way

In a better world, we would start with our facade layer (the moving parts that get the data to bind and place them in a library. We would write a test that ensures that the proper data is pulled on our test environment. The UI page could then be as simple as:

public partial class blog : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        BlogRepeater.DataSource = BlogFacade.GetBlogData();
        BlogRepeater.DataBind();
    }
}

The blog facade class would the contact the database tier object and get the data. This leaves me two classes I can start with tests. The first is my blog facade.

[TestMethod]
public void TestBlogFacade_GetBlogDataSet()
{
     //From set up
     BlogDataSet expected = GetBlogDataExpected();
     BlogDataSet actual = BlogFacade.GetBlogData();

     //Example elaborate test
     for(int i=0; i < expected.Blog.Rows.Length; i++)
     {
          BlogDataSet.BlogRow expectedRow =
                                  
(BlogDataSet.BlogRow)expected.Blog.Rows[i];
          BlogDataSet.BlogRow actualRow= (BlogDataSet.BlogRow)actual.Blog.Rows[i];

          for(int j = 0; j < expectedRow.Columns.Length; j++)
          {
               object expectedValue = expectedRow.Columns[j].Value;
               object actualValue= actualRow.Columns[j].Value;

               Assert.AreEqual(expectedValue, actualValue);
          }
     }
}

I would add some helper methods if this were a real test. The point, however, is not the test, but exercising the code in the blog facade. We can do the same for the data tier classes.

"But I can’t test the ASPX page!" is one complaint I hear to this method. The answer is that you can. One option is nUnitASP, although it is no longer a supported project. But, the other side of the coin is "what do you really have to test on your UI?" Think about this for a second.

  • The submit button might not work – That can be caught in your informal tests. In addition, the Microsoft delegates for event handlers rarely fail without doing something strange in your code. If you have almost no code in your page, the likelihood of CLR code failing is extremely slim.
  • It might not bind properly. Once again we are dealing with something that rarely happens without improper code in your UI.

If 99% of your code lies on the middle tier and back end, how much is likely to fail on your user interface? You might say 1%, but since the majority of the code is simply using Microsoft built methods, the likelihood of any of those methods failing is fairly slim.

Are you saying I should not test my UI? Not at all. But if the majority of your code is on the back end, most UI tests will be either UI testing or acceptance testing. Neither of these types of testing are completely automated in most shops.

Improving Our Tests

Thus far, the tests we have done would call the entire stack (most likely). While I do not have time to write a good example, it is far better to add mocks to the middle tier and data components to supply the data for you. I will have to blog on mocks later, as it is a big topic by itself.

But then I am not testing my data access? No, you are not. But the majority of that is handled by your database server, which reliably hands out data every time you ask. The likelihood of this code going bad is slim, just like event handlers on the UI. If you have stored procedures, you do have a chance of bad code, but unit tests on .NET code are not the best way to test stored procedures.

The mock framework to use is up to you. I have had good luck with Rhino Mocks, but there is even a lightweight mock "engine" included in nUnit these days.

Improving Our Application

On the library side, we have a blog facade that knows too much. The signature is like this:

public BlogDataSet GetBlogData()
{
}

This means that somewhere in the blog facade component, there is a line like this:

string connString = ConfigurationManager.ConnectionStrings["MyConnString"];

Ouch! We should change our signature to something more like this:

public BlogDataSet GetBlogData(string connectionString)
{
}

Note that I am not suggesting that we should not have configuration data lower than the UI. I am just stating that we should feed business layer components and not have them psychically determine how to contact other tiers. If this is UI specific configuration data, it should be passed. In most web application scenarios, that is precisely what this is. If you move from instance of a web application to another instance of the same web application (perhaps co-branded with segregated data), the connection string changes, but it is UI dependent. I am getting tangented. 🙂

The final goal you should keep in mind is this: What if my business owners wanted a XXX app with the same functionality, where XXX could mean Windows Forms application, WPF application, Silverlight application or even a Console Application. Anything in my code that ties me to a particular kind of UI is bad. Enough said.

MVC Framework

One of the neatest things about the MVC Framework, at least for me, is it forces developers to separate concerns. You cannot place code in your UI elements, which forces you to design UIs that act as UIs and business/data logic that acts as business/data logic.

On the downside, it does not force separation to the level it needs to be. You can still write WAY too much code in your controller. As the controller is essentially a facade the finds the model and marries it to a view (yes, this is an oversimplified explanation), it should be very thin. To Microsoft’s credit, they are showing how thin the controller should be in their examples (unlike old ASP.NET applications where they showed you how to jam all of your code either in the page or code behind).

Does this mean I think you should stray away from MVC? Certainly not. It allows you to get one step closer to the UI, shaving away the inability to test "event handlers". In MVC, you can test the controller methods, which are called when your user clicks buttons. I am saying that you can get some of the same benefits that MVC gives you (better yet, forces you into) if you consciously create your own applications as libraries.

For the record, I think the MVC Framework is a great direction for MS to head in. Once the tools catch up with the framework, it will be dynamite.

End Bits

While I rambled a bit, here are two things you should get out of this to make TDD work for your web applications:

  1. Keep your code behind VERY thin. The second you start writing code that does something more than bind (outgoing) or gather (incoming), you are beginning to cross the line towards a monolithic application.
  2. Think of your application as a set of libraries called by UI. Design the working parts of your applications as methods on your backend rather than extensions of event handlers on your front end.

Hope this helps.

Peace and Grace,
Greg

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: