This is a consolidation of my recent high level thoughts on how to mix test driven development with ASP.Net. In my quest for Sanity Driven Development I've gone from having no tests to having too many tests and eventually settled into a nice middle ground. The following guidelines assume certain types of projects:
- Bespoke or heavily customised web or intranet sites.
- Mid sized projects that need some process but still have tight budgets.
- Typical business focused sites dealing with data and dynamic functionality.
There are lots of projects that fall well outside of this scope but it covers the most common type for me, the one that is just big enough to be a real headache when you get it wrong.
Goodbye MVP
I've had a strained relationship with the MVP pattern and we've finally decided to go our separate ways. This might be a bit controversial and goes against a lot I've been hearing for the last few years so give me a moment to explain exactly what I mean and why.
Introducing the MVP pattern to a typical ASP.Net site has one primary goal, increasing testability. Yes, there are other significant advantages to the MVP pattern in general but you already have access to most of these with the ASPX file representing your view and the presenter logic in the code behind. My argument is that with the rest of the development guidelines that follow there will be so little left in the code behind that the benefit of extracting it into yet another layer will not be worth the additional complexity.
There will be times when the MVP or MVC pattern solves a particular problem for you and by all means dust it off and apply it when the time is right. I am simply saying that it probably doesn't offer enough value in the majority of your web pages.
Inexperienced web developers have a tendency to load all the code they can think of in to the code behind, probably because they were never taught any better. The rest of this article is really about extracting that code from the code behind and opening it for better design and easier testing.
Start With The Domain
Incase you haven't heard, it's no longer cool to put your data access code in your code behind. Assuming you have a data access layer of some kind going I strongly recommend you go and read the first half of Domain Driven Design by Eric Evans. Personally I found the later parts of the book were less relevant but the core ideas behind the book are deeply important to the kind of projects we are discussing.
The most critical part is that you move all of your domain knowledge out of the user interface and into the domain layer where it belongs. Here the business concepts driving the project should be presented independently of the technology being used to implement them. This is where you can really let loose with the unit testing and get good returns because you are covering the knowledge that makes this project different from any other.
If you apply the ideas from domain driven design fairly liberally you should end up with very little interesting code remaining in your code behind files. You should end up with a strong model backed up by a number of services which provide the business processes for the application. Most of the code that remains in the code behind files is going to be specific to the implementation of the views in ASP.Net.
Consolidate Your Views
Any business application is likely to end up with a lot of forms. These forms all tend to be fairly similar and building them can be a long and tedious process. Unfortunately there are a huge number of details that go into these forms and they are often details that have a big impact on the end users. Code behind files get filled with lines of code reading values from text boxes, validating them and converting them to other types to be passed to the data access layer.
Look for parts of your user interface that represent business or domain concepts and extract them into a set of common controls. An example of such a concept might be a shipping company that frequently has users entering weights into the site. Weight isn't a concept that is represented anywhere in the ASP.Net controls yet it is critical to the project and comes with it's own set of rules about how the user should be able to interact with it.
The naive approach is to represent weight as a text box on each page and include all the details that go along with it in both the ASPX and the code behind that make up that page. Even a simple example such as weight needs client side validation, server side validation, text parsing, rendering and the layout of the actual controls presented to the user.
Take all of these details and wrap them in a single user control that can be reused throughout the user interface. Make sure that the control takes weights as input from the domain and returns weights as output to the domain. Place the control by itself on a simple page that serves as a test harness so you can use a tool like WatiN to get comprehensive coverage of the client side behaviours. Complex behaviours such as parsing units of weight from input strings should be broken into independent components that can be unit tested in isolation from the control itself.
Creating these mini windows into the domain will simplify your general views and help you to create a much more consistent and powerful user interface. Using combinations of smaller controls you may be able compose larger reusable controls which represent more complete ideas from the domain. Don't forget that by doing this you reduce the amount of code in your user interface and therefore reduce the surface that is difficult to test effectively.
Apply Functional Testing
By now the code behind files should consist mostly of mapping user interface controls and events to the corresponding models and services in the domain. These details define the behaviour of the view and unfortunately are tricky to unit test no matter how many layers of MVP you throw at them.
There is also a huge amount of detail left behind in the implementation of the site that we need to be able to verify. As well as the ASPX files this includes a lot of configuration information wrapped in XML files and the settings of the server itself.
Use an automated functional testing tool such as WatiN to create broader user story style tests which cover the majority of these details. Expect to settle for much less than complete coverage with these tools as the technology is still shaky and performance of the tests is likely to be slower than desired. The advantage is that these tests are much more realistic as they closely mimic real user interactions with the site.
Expand Your Toolbox
As the web matures more and more of the features we add to our sites are implemented outside of the core programming languages we know how to test. Anything labelled Web 2.0 is likely to be poorly covered by traditional methods but fortunately others have already tried to address many of the issues and have left behind clues if not complete tools we can use.
jQuery is an excellent example where the team has provided both an excellent JavaScript client library and a simple framework for writing and running client side JavaScript unit tests. All that remains for us is to use tools such as WatiN to automate the execution of these tests and the collection of the results. W3C and the various standards it promotes are becoming critical to delivery quality projects and once again there are tools and validators that can be easily automated to provide ongoing quality checks for your projects.
The number of technologies that go into a successful site has sky rocketed so try and find ways to automatically verify them. Examples I haven't discussed include the way that search engines crawl the site and how the site will be used or abused by other programmers.
Conclusion
There are a few key themes in these ideas and it shouldn't be any surprise that they are the same ideas used in general software construction. In particular I want to point out clean separation of concerns and careful management of dependencies between layers in your application. Combine these with thorough but pragmatic testing using each tool until it stops delivering value and no longer.
Keep reading, keep learning and keep challenging anything that does not work for you.
Labels: ASP.Net, Style, TDD
21/08/2008 04:16 AM (UTC -07:00)