Journal Articles

CVu Journal Vol 17, #6 - Dec 2005 + Design of applications and programs
Browse in : All > Journals > CVu > 176 (12)
All > Topics > Design (236)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Test Driven Development of C# User Interfaces

Author: Administrator

Date: 02 December 2005 06:00:00 +00:00 or Fri, 02 December 2005 06:00:00 +00:00

Summary: 

In my last article I discussed the values in the Agile Manifesto and what they mean to mean. There are many practices that can be used to make yourself more agile. Short iterations, the planning game, pair programming, and refactoring are a few of the practices present in eXtreme programming. The practice of most value to me, and the practice that many recommend to use as a starting point, is Test Driven Development - TDD.

Body: 

Introduction

In my last article I discussed the values in the Agile Manifesto and what they mean to mean. There are many practices that can be used to make yourself more agile. Short iterations, the planning game, pair programming, and refactoring are a few of the practices present in eXtreme programming. The practice of most value to me, and the practice that many recommend to use as a starting point, is Test Driven Development - TDD.

Once you have mastered test driven development there is a good chance you, like me, will wonder how you ever developed without it. That is a bold claim, and a fair response would be "Why?". Below, I tell you why I find it difficult, or at least very uncomfortable, to develop without automated unit tests - to me it would be like coming to work naked. But first you might want to know what Test Driven Development is. This tutorial should give you a step towards understanding and mastering test driven development - particularly in the arena of C# user interfaces.

What is TDD?

Test Driven Development is an iterative cycle in which you incrementally build the functionality required. Briefly the cycle (or at least my cycle) is:

  • Decide what requirement or part of a requirement to satisfy next.

  • Write an automated test for the requirement.

  • Write the code so that and all other tests past.

  • Refactor the code so that it is of good design and quality and all the tests still pass.

After completing the tutorial I hope you will see that there are finer grained steps to the cycle, e.g:

  • Write test.

  • Compile test - test probably won't compile.

  • Add skeleton so test compiles.

  • Run test - test will fail.

  • Use dummy values to that the test passes.

  • Add real code so the test pasts.

Why Use TDD?

There are many angles to the advantages of TDD. Here are my reasons:

  • Confidence and progress. With each little new test that runs you are making progress. And after the short period it takes to run all the test, you know that all the progress you have made so far is still there. Contrast this will manual testing - with each improvement if you wanted the same confidence you would have to manually repeat all the tests. This would take too long. So most people would only test the part they have change, and perhaps a little more, and hope that they don't break anything.

  • Code quality. When making the test pass, you don't have to worry about code standards, variable names, object structures, readability, re-use, patterns, etc. You only care about getting code that works - doing the simplest thing to make the test pass. When all the tests are running, you can then look for opportunities to improve the design, readability etc of the code knowing that the tests will prove that you haven't broken anything. Without automated unit tests improvements to the design and readability of code require manual re-run of the tests. Most people will shy away from this sticking to their first solution. The result is code that evolves in to a pile of uncontrolled spaghetti.

  • Progress again. It becomes easier to know where you are up to by putting in a test that fails explaining what you were doing, and it becomes easier to decide what to do next.

  • Good design. I have found that a natural by-product of TDD is simple useful classes - probably because they are easier to test.

  • Planning. As each requirement is satisfied you have a measure of how much you have done, how fast you are going, and when you can expect to finish.

  • You know when you are done! When there are no more tests to write you are finished - time to celebrate.

The Unit Test Harness

Testing code using automated tests requires an automated unit test harness of some form. This tutorial was tested using NUnit 2.1.

Reading the Article

For each block of code that is introduced there are a few interesting discussion points identified thus {1}. I would suggest that these are ignored during the first reading.

The Requirements

The problem is stolen from an article by Bill Wake that appeared in Extreme Programming Installed [Jeffries-].

We have bibliographic data with author, title, and year of publication. Our goal is to write a system that can search that information for values we specify. A Paper prototype has produced an interface something like Figure 1 on the following page.

Prototype Interface

Figure 1. Prototype Interface

The user will enter text to search for and click the Find button. Any document that contains that text in the Author, Title or Year will be displayed.

The Context

When developing software it is usual for many to produce and test the 'model' first and in isolation. In brief the model is the part of the application that provides the functionality. The model for this UI has already been produced - the interface for the model (placed in model.cs) is:

using System;
namespace TDD_in_C_Sharp
{
  public interface ISearcher // {2}
  {
    IResult Find(Query q);
  }

  public class Query // {3}
  {
    private string theQuery;
    public Query(String s) { theQuery = s;}
    virtual public String Value // {1}
    { 
      get { return theQuery;}
    }
  }

  public interface IResult 
  {
    int Count();
    IDocument Item(int i);
  }

  public interface IDocument 
  {
    String Author();
    String Title();
    String Year();
  }
}

This should compile defining our interface with the rest of the system.

Interesting Points

  1. I use virtual on all operations of a concrete class. Unfortunately, the default for C# is that functions aren't virtual. However, I know of very few reasons for having non virtual functions.

  2. The model has largely been defined as interfaces rather than classes. There are many potential reasons for this. From a TDD perspective it is because it allows us to use mock objects. When testing a component it is likely that the component will use other components. However, we often don't want to rely on those other components for our tests to run - we'd rather test our component in isolation. The use of interfaces means that in our tests we can substitute the real components with mock ones that we have programmed to supply data and behave as we'd like.

  3. Here one of the objects (Query) is so simple that we haven't bothered creating and interface and mocking it (we don't think that is likely to bite us in the future).

Test Widget Existance

The requirements prototyped a screen design. A first test is to ensure the key controls are on the form: a label, a query field, a table and a button. There may be other components on the form but we don't care about them.[Jeffries-]

In TDD, after establishing what requirement to test, you write a test, and here it is - place in a new file (TestSearchForm.cs) but in the same name space. Note: you will have to add a reference to NUnit.Framework.dll.

using System;
using NUnit.Framework;

namespace TDD_in_C_Sharp
{
  [TestFixture]
  public class UI_Tests
  {
    [Test]
    public void WidgetsPresent() 
    { 
      SearchForm form = new SearchForm();
      Assertion.AssertNotNull(form.searchLabel);
      Assertion.AssertNotNull(form.queryField);
      Assertion.AssertNotNull(form.findButton);
      Assertion.AssertNotNull(form.resultTable);
    }
  }
}

This does not compile because we have not created the form {2}. So create a form as outlined above giving the controls the names used in the tests (e.g. searchLabel). Try to compile. The compilation fails because the tests do not have access to the controls. Make the access internal by modifying the SearchForm class as follows {4}:

public class SearchForm : System.Windows.Forms.Form
{
  internal System.Windows.Forms.Label searchLabel; // {4}
  internal System.Windows.Forms.TextBox queryField;
  internal System.Windows.Forms.Button findButton;
  internal 
     System.Windows.Forms.DataGrid resultTable; //{3}
 ... 

Run your test with NUnit - it should pass.

Interesting Points

  1. We could test the tab order and a few other things (e.g. enablement and visibility) that is simple to do especially if you have a AssertFunction to do it for you.

  2. We have created the test without creating the object being tested - defining the names of the widgets on the form. Thus this code will not compile. In practice the two are created at the same time. The order is not that important. The advantage of doing the test first is that you only write enough of the search panel to get it to compile so that you can run the test. A disadvantage is that you can't take advantage of intellisense.

  3. In my first implementation I used a list view for the table. I did this because it was easy. A principle of TDD is to do the simplest thing that could possibly work. Once you have got your tests running you can consider better ways of working. Later I replaced the list view with the datagrid. Further, you often don't know what the simplest thing is. This means you are going to have to take a diversion to do some investigating - an activity often referred to as a spike.

  4. Defining the accessibility to be internal means that the object, in this case controls, are visible to clients within this assembly. If we wanted to move the test code into a separate assembly, we would have to alter the accessibility or access the controls in some other way.

Test Initial Values

Here we are going to test that the controls are given the correct initial values {1}{2}. Add a new test to the test fixture:

[Test]
public void InitialContents() 
{
  SearchForm form  = new SearchForm();
  Assertion.AssertEquals("Search:", form.searchLabel.Text );
  Assertion.AssertEquals("", form.queryField.Text);
  Assertion.AssertEquals("Find",
                          form.findButton.Text);
  Assertion.Assert(
    "Table starts empty",
    ((DataTable)(form.resultTable.DataSource))
     .Rows.Count == 0);
}

This code requires a reference to System.Data. It also fails because there is no DataTable so create one in the form's constructor:

public SearchForm()
{
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent();

  this.resultTable.DataSource = new DataTable();
}

The cast in the test smells {3}. It is used to get the code working and is necessary because DataSource is of type Object - time to refactor to make it beautiful - readable - we'll do this by providing the DataTable as a property - hiding the ugliness. Change the test first:

[Test]
public void InitialContents() 
{
  SearchForm form  = new SearchForm();
  Assertion.AssertEquals("Search:",
                          form.searchLabel.Text );
  Assertion.AssertEquals("", form.queryField.Text);
  Assertion.AssertEquals("Find",
                          form.findButton.Text);
  Assertion.Assert(
    "Table starts empty", 
    form.ResultTableAsDataTable().Rows.Count == 0);
}

And then add a method to the form to supply the resultTable as a DataTable:

internal DataTable ResultTableAsDataTable()
{
  return  (DataTable) resultTable.DataSource;
}

Interesting Points

  1. Some people believe that automated unit testing of user interfaces is difficult. The approach taken here is to treat the User Interface as an object and test its behaviour as you would any other.

  2. The volume of test that you write in each iteration depends on, amongst other things, confidence in what you are doing and personal style. For example, whether or not controls are present on a form and their initial values could be tested together if your prepared to risk of going faster I normally prefer to take very small steps and build up a consistent momentum.

  3. The concept of foul smelling (offensive) code is an interesting one. It might be hard to read, hard to write or something else might be wrong with it. Either way your instinct tells you that it can be improved and made to smell good. Improving bad smelling code is part of the process of refactoring. That is, improving the code to make is easier to read, easier to extend, easier to maintain, and to remove duplication. Well written code just feels good!

Connect Search Form to a Searcher

So far we have created a form for displaying the search result, and checked its initial values. Now we want to attach the Form to objects and test the form's behaviour to be tested in more detail. First we will test the forms ability to attach to a searcher - any object implements the ISearcher interface. For our tests we will create an object that implements the ISearcher interface - TestSearcher {1}.

As ever we'll start by writing a test describing how we expect to be able to set and get the Form's Searcher.

[Test]
public void SearcherSetup() 
{ 
  ISearcher s = new TestSearcher();
  SearchForm form = new SearchForm(); 
  Assertion.Assert ("Searcher not set",
                    form.Searcher != s); 
  form.Searcher = s; 
  Assertion.Assert("Searcher now set",
                   form.Searcher == s); // [3]
}

This won't compile because we don't yet have a TestSearcher and form doesn't have a Searcher property. So we implement a Skeleton TestSearcher. This in turn requires an IResult. This in turn requires an IDocument. So we'll implement skeletons for all of these. We'll create them in a separate file (mockmodel.cs) to keep the mock components separate:

public class TestSearcher: ISearcher 
{ 
  public virtual IResult Find(Query q) 
  { 
    int count = 0; 
    try {count = Convert.ToInt32(q.Value);} 
    catch (Exception ignored) {} 

    return new TestResult(count); 
  } 

  public virtual Query makeQuery(String s) 
  {
    return new Query(s);
  }
}

And add a Searcher property to our search form [Gamma-]:

private ISearcher mySearcher;
public virtual ISearcher Searcher
{
  get { return mySearcher; }
  set {mySearcher = value; }
}

The test now runs and passes. So we are able to attach a searcher to the form but the form doesn't do much.

Interesting Points

  1. We could use a proper ISearcher. However, we wouldn't have control of the behaviour of the searcher and we don't want to rely on it for our tests to pass. Instead we create an implementation of the interface (a mock object) solely for our testing purposes.

  2. We want to the ability to associate a Searcher (ISearch) with our form. There are two ways of doing it. The Searcher could be supplied as the object is created and/or after the fact and thus allowing it to be changed. I have chosen to allow it to be changed as it makes for an easier tutorial - as ever we do the simplest thing first.

  3. The test uses equality. Here equality uses the identity of the object, i.e. if they are the same object they are equal. An alternative is to use some definition based on the contents. E.g. is they contain the same particular values they are considered equal.

Mock the Searcher

The next thing to do is populate the form from a Searcher - an Object that supplies the ISearcher interface. So we'll create a test searcher that implements ISearcher. The string used in making a search will be an integer that we'll use to tell us how many items to return. We'll name the items a0 (for first author), t1 (second title), etc.

Before implementing TestSearcher we will, of course, write a test to test our TestSearcher.

[Test]
public void Searcher() 
{ 
Assertion.AssertEquals(new Query("1").Value, "1");
// {1} 
  IDocument doc = new TestDocument(1);
  Assertion.AssertEquals("y1", doc.Year());

  IResult result = new TestResult(2); 
  Assertion.Assert(result.Count() == 2); 
  Assertion.AssertEquals("a0",
                         result.Item(0).Author());
  TestSearcher searcher = new TestSearcher(); 
  result = searcher.Find(searcher.makeQuery("2"));
  Assertion.Assert("Result has 2 items",
                   result.Count() == 2); 
  Assertion.AssertEquals("y1",
                         result.Item(1).Year());
}

This fails to compile because TestDocument and TestResult don't have a constructor that takes a single parameter (The integer will be used to name the documents). And the classes (TestSeacher TestResult and TestDocument) have no implementation. So we'll provide these:

public class TestSearcher: ISearcher 
{ 
  public virtual IResult Find(Query q) 
  { 
    int count = 0; 
    try {count = Convert.ToInt32(q.Value);} 
    catch (Exception ignored) {} 
    return new TestResult(count); 
  } 
  public virtual Query makeQuery(String s) 
  {
    return new Query(s);
  }
}

public class TestResult: IResult 
{ 
  int count; 
  public TestResult(int n) {count = n;} 
  public virtual int Count() {return count;} 
  public virtual IDocument Item(int i) 
     {return new TestDocument(i);}
} 

public class TestDocument: IDocument 
{ 
  int count; 
  public TestDocument(int n) {count = n;} 
  public virtual String Author() 
     {return "a" + count;}
  public virtual String Title() 
     {return "t" + count;} 
  public virtual String Year() {return "y" + count;} 
} 

Run the test and it should pass.

Now we can test the display of the search results. When testing objects that can take a number of values it is usually a good idea to test that it works with 0,1 and lots. Let's start with zero.

Interesting Points

  1. Several of the tests use AssertEquals() rather than Assert(). This is because the former can tell you what values weren't equal when the test fails.

Test 0

Here is the code for the test where the number of items found by the test is zero.

[Test]
public void InitialContents() 
{
  SearchForm form  = new SearchForm();
  Assertion.AssertEquals("Search:",
                         form.searchLabel.Text );
  Assertion.AssertEquals("", form.queryField.Text);
  Assertion.AssertEquals("Find",
                         form.findButton.Text);
  Assertion.Assert("Table starts empty",
                   form.ResultTableAsDataTable()
                   .Rows.Count == 0); // [1]
}

This doesn't compile because we don't have a findButton_Click() method on the form. So add an empty one.

private void findButton_Click(object sender, System.EventArgs e)
{
}

This doesn't compile either, findButton_Click is not visible. So declare it as internal so that the test class can see it.... And the tests pass.

Interesting Points

  1. For the initial state there is a choice of having a table that contains no rows or no table at all. We have used the former.

Test 1

[Test]
public void InitialContents() 
{
  SearchForm form  = new SearchForm();
  Assertion.AssertEquals("Search:",
                         form.searchLabel.Text );
  Assertion.AssertEquals("", form.queryField.Text);
  Assertion.AssertEquals("Find",
                         form.findButton.Text);
  Assertion.Assert("Table starts empty", 
     form.ResultTableAsDataTable().Rows.Count == 0);
}

The test fails because findButton_Click() has no implementation.

When the button is clicked, the string in the text field is translated into a query and the searcher finds a result for display in the table. However, there is a type mis-match: the Searcher gives us an IResult, but the display table needs a DataSource.

Thus, we need something to adapt the IResult output to the DataSource needed. To achieve this we will create an Adapter class that is given an IResult and provides the DataTable interface. {1}

At this point I was tempted to write a test for the adapter but, doing the simplest possible thing, the adapter only has to do enough to satisfy the buttons needs so instead we'll start by implementing the button as if the adapter existed. In the form:

internal void 
findButton_Click(object sender, System.EventArgs e)
{
  Query q = new Query(queryField.Text);
  resultTable.DataSource = new DataTableAdapter
     (Searcher.Find(q));
}

And to make it compile we'll stub the class placed class in its own file (DataTableAdaptor.cs).

using System;
using System.Data;
namespace TDD_in_C_Sharp
{
  public class DataTableAdapter :  DataTable
  {
    public DataTableAdapter(IResult theResult)
    {
    }
  }
}

The test fails as we need to implement the constructor:

public DataTableAdapter(IResult theResult)
{
  this.Columns.Add("Author");
  this.Columns.Add("Title");
  this.Columns.Add("Year");
  for (int index=0; index < theResult.Count();
       index++)
  {
    IDocument doc = theResult.Item(index);
    DataRow newRow = this.NewRow();
    newRow["Author"] =
       theResult.Item(index).Author();
    newRow["Title"] = theResult.Item(index).Title();
    newRow["Year"] = theResult.Item(index).Year();
    this.Rows.Add(newRow);
  }
}

Test1 now passes. And so does test N:

[Test]
public void TestN() 
{
  ISearcher searcher = new TestSearcher();
  SearchForm form = new SearchForm(); 
  form.Searcher = new TestSearcher(); 
  form.queryField.Text="5"; 
  form.findButton_Click(form.findButton ,
     new System.EventArgs ()); 
  Assertion.Assert("5-row result",
     form.ResultTableAsDataTable().Rows.Count == 5); 
  Assertion.AssertEquals("a0",
     form.ResultTableAsDataTable().Rows[0][0] );
  Assertion.AssertEquals("t3", 
     form.ResultTableAsDataTable().Rows[3][1] );
  Assertion.AssertEquals("y4", 
     form.ResultTableAsDataTable().Rows[4][2] );
}

Interesting Points

  1. Adapter is a design pattern described in more detail in Design Patterns [Gamma-]

Test For Left Overs

After running one search we want to make sure that the results aren't present in any subsequent search.

public void LeftOvers() 
{ 
  ISearcher searcher = new TestSearcher();
  SearchForm form = new SearchForm(); 
  form.Searcher = new TestSearcher(); 
  form.queryField.Text="5"; 
  form.findButton_Click(form.findButton,
     new System.EventArgs()); 
Assertion.Assert(form.ResultTableAsDataTable()
     .Rows.Count == 5); 
  form.queryField.Text="3"; 
  form.findButton_Click(form.findButton ,
     new System.EventArgs()); 
  Assertion.Assert(form.ResultTableAsDataTable()
     .Rows.Count == 3); 
}

This test passes.

Test the User Interface

Some times you might want to test some aspects of the appearance of the user interface. For example, the relative position of controls or overlapping controls. I haven't yet found it necessary considering that aspect to be part of functional testing. It is, however, useful to test the tab order:

[Test]
public void TabOrder()
{
  SearchForm form = new SearchForm(); 
  NUnit.Forms.FormAssertions.AssertTabOrder(form,
    "queryField,findButton,resultTable");
}

Refactoring - Improving the Design

We have got our application working - GREAT! Quality developers would now work hard to improve the design - if possible.

The design metaphor used mirrored that used by Bill Wake in his original article. It has given a basis for TDD of C# (and .net) user interfaces. However, it is frequently stated that UI layer should be very thin. What does this mean? Is the design so far thin enough?

The Humble dialog [dialog] article argues that the behaviour of the dialog (or Form) should be placed in an intermediate 'Smart' object. The Smart object knows what should be displayed and tells the form to display it. It knows how to respond to events such as button clicks and the form should ask it to take the appropriate response to a click. The form then becomes a set of properties defining what to display, and a set of event handlers that delegate to the Smart object. Testing involves driving this smart object with Mock UI objects.

Martin Fowler suggests a similar approach in his article Model View Presenter. Back in 1994 Phran Ryder, in his MSc Thesis, used a similar approach in which he referred to the smart object as the view-controller.

What Are the Pros and Cons of this Approach?

Cons
  1. It makes it more difficult to take advantage of the ADO set of objects. Microsoft's ADO controls and classes can make it easier to rapidly create an application. Such applications are to a great extent the Smart GUI described in Domain Driven Design [Evans].

  2. When the application is simple why go to the trouble of adding another layer.

Pros
  1. The idea that forms should be properties and delegating event handlers makes it easy to see whether the form is doing anything it shouldn't. It is be important that the form only contains behaviour that is relevant to its responsibilities.

  2. Decouples the form and the model. Without the smart object, the form could be coupled to many parts of its model. The smart object would deal with all that coupling. Splitting the form into two would be easy. But if you wanted to split the form in two you could create the intermediate object at that point.

So have we got anything in our form that should not be there?

  1. We have a mySearcher bound to the model - the form need not know about searchers.

  2. We have code binding the data source to an ADO object in this case a dataTable.

  3. We have a Find button code to perform the query and bind the ADO result to the data source - this code could be moved to the smart object.

  4. All the initialisation code is created by .net. Can't do much about this.

So there are a few opportunities for improvement....

References

[Jeffries-] Extreme Programming Installed. Ron Jeffries, Ann Anderson, Chet Hendrickson. Addison Wesley 2001. ISBN: 0-201-70842-6

[Gamma-] Design Patterns:Elements of Reusable Object-Oriented Software. Eric Gamma, Richard Helm, Ralph Johnson, John Vlissides. Addison-Wesley 1995. ISBN: 0 201 63361 2

[Evans] Domain-Driven Design: Tackling Complexity in the Heart of Software. Eric Evans. Addison-Wesley 2004 .ISBN: 0 321 12521 5

Notes: 

More fields may be available via dynamicdata ..