By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
435,131 Members | 1,437 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 435,131 IT Pros & Developers. It's quick & easy.

Test Driven Development with Windows Forms

P: n/a
I've just released an article on using Test Driven Development with C# and
Windows Forms. GUI's are often difficult to test, so I thought it might be
of interest.

The article along with the example source code can be downloaded here:

http://www.blogitek.com/timhaughton/...0And%20TDD.zip
The article text is below. Not sure what it will do to the formatting when
it hits Usenet world. Downloading the zip might be easiest.

--
Regards,

Tim Haughton

Agitek
http://agitek.co.uk
http://blogitek.com/timhaughton

**********

After a conversation on the TestFirstUserInterfaces list on Yahoo regarding
TDD'ing forms that handle events and launch dialogues, I've gathered my
thoughts and put fingers to keyboard. By happy coincidence, the topic
coincided with some work I was doing, so I was able to spend some time
looking at the problem.

At Ron Jeffries' request, I thought I'd write a little 'thing' showing how
this might work in practice. For this little example, we're going to write
an application whose main form displays some text in a font the user
specifies. Just to make sure we're close enough to the original post, we'll
have a button that when clicked, launches a font selection dialogue.
Naturally we'll proceed test first.

House keeping: I'm writing this using VS2005, C#, NUnit and a modified
version of NMock, the modifications to which I'll include in the source code
to this example.

I'm not going to test drive the initial layout of the form as it's just not
interesting from the perspective of this example. So we'll start from here:

using System;

using System.Drawing;

using System.Windows.Forms;

namespace TestApp

{

static class Program

{

[STAThread]

static void Main()

{

Application.EnableVisualStyles();

Application.SetCompatibleTextRenderingDefault( false );

Application.Run( new MainForm() );

}

}

class MainForm :Form

{

private Button button;

private Label label;

public MainForm()

{

button = new Button();

label = new Label();

this.Width = 400;

this.Height = 250;

button.Location = new Point( 5, 10 );

button.Text = "Select Font";

button.Width = 75;

label.Text = "FooBar";

label.Location = new Point( 85, 10 );

label.Width = 300;

label.Height = 200;

Controls.Add( button );

Controls.Add( label );

}

}

}

OK, so the first thing we'd like to see is a font selection dialogue be
shown when the user when the button is pressed. Straight away we're into
murky water. Showing a dialogue during a test is not generally considered a
helpful thing. We need some way to prompt the user for some information, and
we also need a way to test.

What areas do we need to test?

1.. When the button is clicked, some action is taken by the form to
solicit the user input.
2.. A font selection dialogue is displayed.
3.. If the user accepts the change to the font, the change is applied to
the label.
4.. If the user rejects the change to the font, no change is applied to
the label.
This is where the User Interrogator steps in to lend a hand. We will
separate the notion of requesting information from the user, and the UI
mechanisms used to achieve this goal.

I'm going to create a new interface to help with this task, so, to the
tests:

[TestFixture]

public class MainFormSpec

{

[Test]

public void FormShouldCallGetFontFromUserWhenButtonIsClicked()

{

DynamicMock mock = new DynamicMock( typeof(IUserInterrogator) );

MainForm form = new MainForm(
(IUserInterrogator)(mock.MockInstance) );

mock.Expect( "GetFontFromUser" );

ControlTester ct = new ControlTester( form );

ct["Button"].Events["Click"].Fire();

mock.Verify();

}

}

I'm cheating a little here by using some useful lightweight Control testing
classes, which I've included in the example source code. Naturally this
doesn't compile, so we need to do a smidgeon of work to get a build:

public interface IUserInterrogator

{

}

A change to the MainForm's constructor:

public MainForm( IUserInterrogator interrogator )

{

button = new Button();

label = new Label();

...

And a tweak of the entry point method:

Application.Run( new MainForm( null ) );

We now have a build, and a red light. Time to go for green. All we're
interested in at this stage is showing that when the button is clicked, the
form calls the GetFontFromUser method on the interrogator object. So, a
little tweak to the form's constructor, hook up an event handler for the
button's Click event and have it call the GetFontFromUserMethod.

public MainForm( IUserInterrogator interrogator )

{

this.interrogator = interrogator;

button = new Button();

label = new Label();

this.Width = 400;

this.Height = 250;

button.Name = "Button";

button.Location = new Point( 5, 10 );

button.Text = "Select Font";

button.Width = 75;

button.Click += new EventHandler( button_Click );

...

and,

void button_Click(object sender, EventArgs e)

{

interrogator.GetFontFromUser();

}

Groovy! We have our first green light. This tells us that we're heading in
the right direction, but it's not terribly useful as the GetFontFromUser
doesn't actually return a font - let's put that right. We'll add a new test
to ensure that the form uses the returned Font correctly.

[Test]

public void FormShouldGetFontFromUserAndApplyToLabelWhenButton IsClicked()

{

DynamicMock mock = new DynamicMock( typeof( IUserInterrogator ) );

MainForm form = new MainForm( (IUserInterrogator)(mock.MockInstance) );

Font wingDings = new Font( "WingDings", 10F );

ControlTester ct = new ControlTester( form );

Label label = ct["Label"].control as Label;

Assert.AreNotEqual( wingDings, label.Font );

mock.ExpectAndReturn( "GetFontFromUser", wingDings, null );

ct["Button"].Events["Click"].Fire();

Assert.AreEqual( wingDings, label.Font );

}

So, we need to name our label "Label" to satisfy the ControlTester,

label.Name = "Label";

change the interface to return a font,

public interface IUserInterrogator

{

Font GetFontFromUser();

}

and wire up the event handler properly:

void button_Click(object sender, EventArgs e)

{

label.Font = interrogator.GetFontFromUser();

}

Green light. OK, but we've got some duplication here between the tests. Our
new test method is implicitly doing the job of the first test, so let's
remove the first test.

The next step is to provide a real implementation of IUserInterrogator for
the form to use. We've already tested the interaction between the
interrogator and the form by using a mock, so when we've TDD'd our concrete
interrogator, it should just plug straight in. Earlier on, we swam out of
murky water by delegating the displaying of the font selection dialogue to
the interrogator. It's finally caught up with us, so what are we going to
do?

Let's write a test.

[TestFixture]

public class UserInterrogatorSpec

{

[Test]

public void ShouldReturnCorrectFontWhenUserSelectsWingdingsAnd Accepts()

{

Form form = new Form();

UserInterrogator interrogator = new UserInterrogator( form );

Font f = interrogator.GetFontFromUser();

Assert.AreEqual( new Font( "Wingdings", 10F ), f );

}

}

That basically encapsulates what we want to do, but at the minute we don't
have a build, so let's write a stub.

public class UserInterrogator : IUserInterrogator

{

public UserInterrogator(Form owner)

{

}

public Font GetFontFromUser()

{

return null;

}

}

Now we've got a build, and a red light. In going for green, we'll write a
nave implementation just to crystallise our understanding of the problem.

public class UserInterrogator : IUserInterrogator

{

private Form owner;

public UserInterrogator(Form owner)

{

this.owner = owner;

}

public Font GetFontFromUser()

{

FontDialog fd = new FontDialog();

fd.ShowDialog( owner );

return fd.Font;

}

}

Running the tests now results in a FontDialogue popping up. This is just no
good at all. We need to separate construction and operation. In order to do
this, we'll need to add a new interface. Looking at the implementation has
also brought to my attention the need for the current font to be sent to the
GetFontFromUser method, so lets make the necessary changes now.

[TestFixture]

public class MainFormSpec

{

[Test]

public void
FormShouldGetFontFromUserAndApplyToLabelWhenButton IsClicked()

{

DynamicMock mock = new DynamicMock( typeof( IUserInterrogator ) );

MainForm form = new MainForm(
(IUserInterrogator)(mock.MockInstance) );

Font wingDings = new Font( "WingDings", 10F );

ControlTester ct = new ControlTester( form );

Label label = ct["Label"].control as Label;

Assert.AreNotEqual( wingDings, label.Font );

mock.ExpectAndReturn( "GetFontFromUser", wingDings, new object[] {
label.Font } );

ct["Button"].Events["Click"].Fire();

Assert.AreEqual( wingDings, label.Font );

}

}

[TestFixture]

public class UserInterrogatorSpec

{

[Test]

public void ShouldReturnCorrectFontWhenUserSelectsWingdingsAnd Accepts()

{

Form form = new Form();

UserInterrogator interrogator = new UserInterrogator( form );

Font f = interrogator.GetFontFromUser( new Font( "Arial", 10F ));

Assert.AreEqual( new Font( "Wingdings", 10F ), f );

}

}

and,

public Font GetFontFromUser( Font initialFont )

{

FontDialog fd = new FontDialog();

fd.Font = initialFont;

fd.ShowDialog( owner );

return fd.Font;

}

with,

void button_Click(object sender, EventArgs e)

{

label.Font = interrogator.GetFontFromUser( label.Font );

}

I'm forgoing the red green refactor mantra for this in the name of
expediency. Now, we need to delegate the showing of the dialogue in a way
that allows us to test, so I create this interface:

public interface IDialogLauncher

{

Font ShowAndReturn(FontDialog fontDialog);

}

And inject allow for injecting this dependency in to the UserInterrogator:

public class UserInterrogator : IUserInterrogator

{

private Form owner;

private IDialogLauncher launcher;

public UserInterrogator(Form owner, IDialogLauncher launcher)

: this( owner )

{

this.launcher = launcher;

}

...

This now allows us to make some headway. Firstly, let's change the test,

[TestFixture]

public class UserInterrogatorSpec

{

[Test]

public void ShouldReturnCorrectFontWhenUserSelectsWingdingsAnd Accepts()

{

DynamicMock mock = new DynamicMock( typeof( IDialogLauncher ) );

FontDialog fd = new FontDialog();

fd.Font = new Font( "Arial", 10F );

And constraint = new And( new IsTypeOf( typeof( FontDialog ) ), new
NotNull() );

mock.ExpectAndReturn( "ShowAndReturn", new Font( "Wingdings", 10F ),
constraint );

Form form = new Form();

UserInterrogator interrogator = new UserInterrogator( form,
mock.MockInstance as IDialogLauncher );

Font f = interrogator.GetFontFromUser( fd.Font );

mock.Verify();

Assert.AreEqual( new Font( "Wingdings", 10F ), f );

}

}

giving us a red light, and now we can change the GetFontFromUser method to
delegate showing the dialogue:

public Font GetFontFromUser(Font initialFont)

{

FontDialog fd = new FontDialog();

fd.Font = initialFont;

return launcher.ShowAndReturn( fd );

}

giving us green.

In the event of the user clicking on 'Cancel' on the font selection
dialogue, we'd like our ShowAndReturn method to return null and we want our
GetFontFromUser method to return the initial font. So a new test looks like:

[Test]

public void ShouldReturnInitialFontWhenUserCancels()

{

DynamicMock mock = new DynamicMock( typeof( IDialogLauncher ) );

FontDialog fd = new FontDialog();

fd.Font = new Font( "Arial", 10F );

And constraint = new And( new IsTypeOf( typeof( FontDialog ) ), new
NotNull() );

mock.ExpectAndReturn( "ShowAndReturn", null, constraint );

Form form = new Form();

UserInterrogator interrogator = new UserInterrogator( form,
mock.MockInstance as IDialogLauncher );

Font f = interrogator.GetFontFromUser( fd.Font );

mock.Verify();

Assert.AreEqual( fd.Font, f );

}

The trivial change to GetFontFromUser yields:

public Font GetFontFromUser(Font initialFont)

{

FontDialog fd = new FontDialog();

fd.Font = initialFont;

Font newFont = launcher.ShowAndReturn( fd );

if ( newFont != null )

return newFont;

return initialFont;

}

A little refactoring of the tests here leaves us with

[TestFixture]

public class UserInterrogatorSpec

{

[Test]

public void ShouldReturnCorrectFontWhenUserSelectsWingdingsAnd Accepts()

{

SetUpAndCall(new Font( "Wingdings", 10F ), new Font( "Wingdings",
10F ));

}

[Test]

public void ShouldReturnInitialFontWhenUserCancels()

{

SetUpAndCall( null, new Font( "Arial", 10F ) );

}

private static void SetUpAndCall(Font mockReturn, Font returnFont)

{

DynamicMock mock = new DynamicMock( typeof( IDialogLauncher ) );

FontDialog fd = new FontDialog();

fd.Font = new Font( "Arial", 10F );

And constraint = new And( new IsTypeOf( typeof( FontDialog ) ), new
NotNull() );

mock.ExpectAndReturn( "ShowAndReturn", mockReturn, constraint );

Form form = new Form();

UserInterrogator interrogator = new UserInterrogator( form,
mock.MockInstance as IDialogLauncher );

Font f = interrogator.GetFontFromUser( fd.Font );

mock.Verify();

Assert.AreEqual( returnFont, f );

}

}

We're still on green, so we can move on to developing our concrete class
DialogLauncher. We need to specify a couple of pieces of behaviour; firstly,
that the ShowAndReturn method calls ShowDialogue on the font selection
dialogue passing in the parent form as an argument. Secondly, that if the
user clicks OK, the method returns the currently selected font, otherwise it
returns null.

In order to do this, we're going to need one of our mods to NMock, namely
MarshalByRefMock. This is similar to the DynamicMock class with 2
differences, it can mock only those object which derive from
MarshalByRefObject, but can mock all methods, including non-virtual ones.
This makes it ideal for mocking Controls and Forms.

First, we'll specify the behaviour in the event the user clicks cancel.

[TestFixture]

public class DialogLauncherSpec

{

[Test]

public void ShouldReturnNullWhenUserClicksCancel()

{

Form parent = new Form();

DialogLauncher dl = new DialogLauncher( parent );

MarshalByRefMock mock = new MarshalByRefMock( typeof(
FontDialog ) );

mock.ExpectAndReturn( "ShowDialog", DialogResult.Cancel, parent );

Assert.IsNull( dl.ShowAndReturn( mock.MockInstance as
FontDialog ) );

mock.Verify();

}

}

The implementation seems obvious, so let's go straight for green.

public class DialogLauncher : IDialogLauncher

{

Form parent;

public DialogLauncher(Form parent)

{

this.parent = parent;

}

public Font ShowAndReturn(FontDialog fontDialog)

{

fontDialog.ShowDialog( parent );

return null;

}

}

We can now spec the case of the user clicking OK.

[Test]

public void ShouldReturnSelectedFontWhenUserCLicksOK()

{

Form parent = new Form();

DialogLauncher dl = new DialogLauncher( parent );

MarshalByRefMock mock = new MarshalByRefMock( typeof( FontDialog ) );

Font expectedFont = new Font( "Wingdings", 12f );

mock.ExpectAndReturn( "ShowDialog", DialogResult.OK, parent );

mock.ExpectAndReturn( "get_Font", expectedFont, null );

Assert.AreEqual( expectedFont, dl.ShowAndReturn( mock.MockInstance as
FontDialog ) );

mock.Verify();

}

And the corresponding change to the ShowAndReturn method,

public Font ShowAndReturn(FontDialog fontDialog)

{

if ( fontDialog.ShowDialog( parent ) == DialogResult.OK )

return fontDialog.Font;

return null;

}

With that green light, we're just about done. All that remains is to ensure
that when the MainForm and UserInterrogator are instantiated without an
injected dependency, they avail themselves of concrete implementations of
the UserInterrogator and DialogLauncher respectively.

[TestFixture]

public class UserInterrogatorSpec

{

[Test]

public void ShouldCreateADialogLauncherWhenOneIsNotSpecified()

{

UserInterrogator ui = new UserInterrogator( new Form() );

Assert.IsNotNull( ui.interrogator );

}

...

leads to

public class UserInterrogator : IUserInterrogator

{

private Form owner;

internal IDialogLauncher launcher;

public UserInterrogator(Form owner, IDialogLauncher launcher)

{

this.owner = owner;

this.launcher = launcher;

}

public UserInterrogator(Form owner)

: this ( owner, new DialogLauncher( owner ) )

{

}

...

and

[TestFixture]

public class MainFormSpec

{

[Test]

public void ShouldCreateAUserInterrogatorWhenOneIsNotSpecified ()

{

MainForm mf = new MainForm();

Assert.IsNotNull( mf.interrogator );

}

...

leads to

class MainForm : Form

{

private Button button;

private Label label;

internal IUserInterrogator interrogator;

public MainForm()

: this(null)

{

this.interrogator = new UserInterrogator( this );

}

...

There we go. Job done! At this point, I recommend that developers do the
victory dance. If you don't have one, get one, it really makes you feel
good.

Summary
The solution I arrived at here after this coding experiment is different to
the solution I arrived at originally on the TFUI list via a thought
experiment. It once again serves to remind me to question the value of
thought alone. Thinking is good, testing and coding is often better.

As to the solution, there's a clear pattern here that should be reusable.
Whenever a UI has to respond to an event, and query the user for some
interaction, the way to test drive this is by separating the 3 concerns:

1.. Responding to the event and conceptually asking the user for input.
2.. Creating the UI elements to present to the user.
3.. Displaying the UI elements.
Hopefully, this strategy should help in the noble pursuit of TFUI. Usually,
descriptions of test driven user interfaces suggest a thin layer of
untestable GUI code at the top. This approach has 100% test coverage of the
GUI. Key to this is the mocking of user controls and forms.


Nov 11 '05 #1
Share this question for a faster answer!
Share on Google+

This discussion thread is closed

Replies have been disabled for this discussion.