Test-Driven Development
"Agile" and "extreme programming" have been around for quite awhile, and it's quickly dominating corporate development culture as the fastest and least expensive means of getting large projects finished in a high-quality manner. One of the core tenents of this philosophy is test-driven development. As we mentioned, test-driven development requires that you write testing code first and write your code to satisfy those tests.
An open-source tool called NUnit ( http://nunit.org) is a free application that allows you to unit test your code using "test fixtures," special classes that contain suites of tests. After you install NUnit, its classes will be installed in the global assembly cache, so they'll appear in the .NET tab of the Add References dialog of Visual Studio.
NUnit is inspired by JUnit, a similar tool created by the Java community to unit test Java code. Many of the open-source testing and building tools in the .NET world are directly inspired by Java counterparts.Figure 14.2 showed the Add Reference dialog. You'll also need a reference to the NUnit.Framework.dll and System.Web.dll assemblies in your test project (these are not added to class library projects by default). Figure 16.1 shows the Solution Explorer with a test project referencing both the production class library (in this case named "CliqueSite.Ads") and the NUnit framework. Note that we'll also need to add using (Imports in VB.NET) statements in our class file, as you'll see in Listing 16.1.
Figure 16.1. Solution Explorer showing project and NUnit references.

A class that contains tests must be marked with the TestFixture attribute, and it must be declared public. Similarly, individual tests in the class should be public methods that don't return anything (void in C#, Sub in VB.NET), and each method needs the Test attribute. You can optionally create methods that are marked with the TestFixtureSetUp and TestFixtureTearDown attributes to do some initialization before the tests are run and to clean up afterward.
Because we're going to execute code completely out of the context of the ASP.NET environment, we need to simulate it. To do this, we need a spot of setup code to create an HttpContext that we can work with. That code goes in our setup method, which is shown together with the rest of our initial test code in Listing 16.1. Replace the application path with the actual location of your test assembly.
Listing 16.1. A bare-bones test fixture
C#
using System;
using System.IO;
using System.Web;
using System.Web.Hosting;
using NUnit.Framework;
using CliqueSite.Ads;
namespace CliqueSite.Ads.Testing
{
[TestFixture]
public class SiteTest
{
[TestFixtureSetUp]
public void Setup()
{
TextWriter tw = new StringWriter();
HttpWorkerRequest wr = new SimpleWorkerRequest(
"/",
"C:\\applicationpath",
"default.aspx", ", tw);
HttpContext.Current = new HttpContext(wr);
}
[Test]
public void FirstTest()
{
}
}
}
VB.NET
Imports System
Imports System.IO
Imports System.Web
Imports System.Web.Hosting
Imports NUnit.Framework
Imports CliqueSite.Ads
Namespace CliqueSite.Ads.Testing
<TestFixture()> Public Class SiteTest
<TestFixtureSetUp()> Public Sub Setup()
Dim tw = New StringWriter()
Dim wr = New SimpleWorkerRequest("/", "C:\applicationpath", _ "default.aspx", ", tw)
HttpContext.Current = New HttpContext(wr)
End Sub
<Test()> Public Sub FirstTest()
End Sub
End Class
End Namespace
With all our skeleton test code in place, we're ready to write tests! Keep in mind that, at this point, we haven't written any production code. Let's say that our goal is to write a custom data class, similar to the one in Chapter 5, "Object-Oriented Programming Applied: A Custom Data Class," where we mimic columns in a table called "Site" with properties in our class, and we use create, update, and delete methods to manipulate data in the database. Our first design choice is probably to have a constructor that populates the object with default values. To test this, we'll create the code in Listing 16.2 (we'll leave out the VB.NET version because it's so straightforward).
Listing 16.2. Our first test
[Test]
public void FirstTest()
{
Site s = new Site();
Assert.AreEqual(0, s.SiteID);
Assert.AreEqual(String.Empty, s.Contact);
Assert.AreEqual(String.Empty, s.Email);
Assert.AreEqual(String.Empty, s.Phone);
Assert.AreEqual(String.Empty, s.SiteName);
Assert.AreEqual(String.Empty, s.SiteUrl);
}
If we try to compile our test class library project, it won't work because it has absolutely no idea what the Site class is. This means our test has failed, and that's a good thing! Our code tries to create an instance of the Site class, which doesn't exist. Then it uses the Assert class and its static AreEqual method from the NUnit framework to compare two values. We'll see what these assertions do in a moment, but note that Assert has several other useful methods such as IsNull() and IsFalse(), among others.
At this point, we're ready to write some real production code in our production class library. Our goal in writing this code is simply to pass the test we've writtenno more and no less. To make test-driven development work, we must stay mercilessly focused on passing the tests and nothing else. These tests determine the requirements, so there's no need to interpret them to be something else, a habit that you might have during the conventional coding process. To pass the test, our test code needs to be able to compile first of all, and then when it's executed in NUnit (we're getting to that soon, I promise), the new Site object will have the default values we've indicated in our test. Listing 16.3 shows what that code will look like.
Listing 16.3. The start of our class, designed to pass the test in Listing 16.2
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
namespace CliqueSite.Ads
{
public class Site
{
public Site()
{
_siteID = 0;
_siteUrl = ";
_siteName = ";
_contact = ";
_email = ";
_phone = ";
}
private int _siteID;
public int SiteID
{
get {return _siteID;}
set {_siteID = value;}
}
//
// more properties and private members here
//
private string _phone;
public string Phone
{
get {return _phone;}
set {_phone = value;}
}
}
}
This simple code creates a number of properties and private members, and a constructor that assigns default values. That's what we want to perform, according to our test. Now when we attempt to build our solution, Visual Studio will build the production class library first because it's referenced by the test class library. The assembly is copied into test library's project, and with its reference, the test library compiles! Finally, we get to use NUnit.
When we start NUnit, we're asked to save a new project. Choose the same folder as the compiled test assembly, which from the root project folder will be \bin\Debug. Next, choose Project and then Add assembly from the menu, and select the assembly that contains our test (ours was called CliqueSite.Ads.Testing.dll). Items appear in the following order (with gray spots next to them): A tree that starts with the NUnit project file, then the assembly, then the elements of the namespace, then the test fixture class, and finally our tests.
This is where the magic happens. Click the Run button. NUnit loads the assembly and looks for the classes marked with the TestFixture attribute, and then in those classes, it searches for methods marked with the attributes we mentioned earlier. In our example, it finds the SiteTest class and runs the Setup method because it's marked with the TestFixtureSetup attribute. Next it finds methods with the Test attribute. We have only one, and that's FirstTest. When NUnit gets to a line with one of the Assert methods, it tests to see if the condition is true. If it is true, it moves on. If not, it stops and moves on to the next test. If all the tests pass, the "lights" in the tree turn green and a big green bar is shown under the Run button, showing that we've passed our tests! Figure 16.2 shows the happy NUnit.
Figure 16.2. Nunit, with all our tests passed.
Chapter 5. The difference this time is that we're writing these tests first, before we write the actual code. Finally, to be sure we're really going to get it right with our final code (again, before the actual code is written), we can write a more comprehensive test like the one in Listing 16.4.
Your application will likely contain configuration information that includes connection strings and other information. Because you're running outside of a normal ASP.NET process, your web.config file should be placed in the same folder as your .nunit project file. It should have the same name, replacing .nunit with .config. For example, if your project name is Project1.nunit, name your config file Project1.config.
Listing 16.4. A more comprehensive test to supplement our smaller tests
[Test]
public void CrudTest()
{
Site s = new Site();
s.Contact = "jeff";
s.Email = "jeff@popw.com";
s.Phone = "555.1212";
s.SiteName = "MySite";
s.SiteUrl = "http://www.popw.com";
int siteID = s.Create();
s = new Site(siteID);
Assert.AreEqual("jeff", s.Contact);
Assert.AreEqual("jeff@popw.com", s.Email);
Assert.AreEqual("555.1212", s.Phone);
Assert.AreEqual("MySite", s.SiteName);
Assert.AreEqual("http://www.popw.com", s.SiteUrl);
s.Contact = "jeffz";
s.Email = "jeff@popw.comz";
s.Phone = "555.1212z";
s.SiteName = "MySitez";
s.SiteUrl = "http://www.popw.comz";
s.Update();
s = new Site(siteID);
Assert.AreEqual("jeffz", s.Contact);
Assert.AreEqual("jeff@popw.comz", s.Email);
Assert.AreEqual("555.1212z", s.Phone);
Assert.AreEqual("MySitez", s.SiteName);
Assert.AreEqual("http://www.popw.comz", s.SiteUrl);
s.Delete();
Assert.AreEqual(0, s.SiteID);
s = new Site(siteID);
Assert.AreEqual(0, s.SiteID);
}
This test runs the whole range of manipulating a Site object. Keeping in mind that our Site class will be similar to the example class in Chapter 5 (which is why we aren't going to actually write it here), we first create a Site object, assign its properties, call the Create() method to persist the values to a database, and then read them back to see if they match. We update the values and then read them back to see if they match. Finally, we delete the record and ensure that the SiteID property returns 0, just as we specified. Our testing project won't compile, of course, because so far we haven't written the Create(), Update(), and Delete() methods. After we do, we can compile and run the tests.
It might seem like a lot of work to develop in this manner, but consider these benefits. First, you are creating code that is only meant to pass a test, which is a single requirement. You are sure of what you need to do, so you don't become a victim of scope creep. The test defines a specific requirement, and you stick to it.
Your finished code is also more likely to be bug-free. Although no developer can entirely separate himself or herself from the code he or she writes, this methodology allows for a certain amount of bias removal. Writing tests that tested code you already wrote would be unfair and biased because you would be testing what you think your code is supposed to do. Writing the tests firsts helps reduce that bias.
Best of all, you can have high confidence in your code because it has been so thoroughly tested. If code somewhere else in your project is incompatible, it will cause tests to fail. You don't have to worry about breaking something else because the failed tests will tell you right away where the problem is.
The one remaining issue is that you may have to debug your code when something isn't working right. To do this, you must attach the debugger to a running process, in this case, to NUnit. Figure 16.3 shows the Attach to Process dialog found under Visual Studio's Debug menu.
Figure 16.3. Attach to Process dialog.
Chapter 14, "Developing with Visual Studio"), you can do the very same here. All the same information is available, as well as the ability to step through your code.
Additionally, you can use the static method Console.WriteLine() to write out strings to NUnit's Console Out tab. This is similar to using trace.Write() in a page.
Hopefully you can see the benefit of test-driven development. It appears to be more work at first, but the more you do it, the more you will see that the quality of your code increases.