Friday, April 04, 2008

Testing WinForms

by J.P.

Testing a WinForms application written in a "traditional" manner is just about impossible. In order for events to fire (like Load), ultimately the window needs to be able to process messages, meaning it has to be visible. As you can guess, this is not very conducive to automated unit testing.

If you happen to be reading this, you know there are several free options for unit testing WinForms. But, I am guessing that since you are reading this, you think they suck. And you know what? I think they suck, too. 

You probably also know there are a few very expensive applications that are very difficult to use and require you to hire a full time guy to configure the tool and noodle around with it all day. Not only that, to get it, you need to call a sales person, because the vendor doesn't list a price...or show any screenshots of the tool for that matter. My rule of thumb: If the vendor doesn't list the price, it's not worth the price.

So, what are the alternatives? Well, many of you, like me, use the Model View Presenter pattern. The idea here is that all of the important code ends up in a Presenter class that is easy to test. Throw in a little Rhino Mocks and then it really does become trivial to write tests like "When I select the make of a car, a list of models should be displayed". But even though you can write a million tests like this against the Presenter, at the end of the day your coverage on your Form ends up a big, fat 0%.

Is this ok? Maybe.

I can think of several scenarios that make me think I would want to test my form directly. For one thing, it might be nice to know that my Presenter was being used correctly by the Form. For instance, when the Load event fires, does Presenter.Initialize() get called? When SelectChangeCommitted is raised on my "makes" ComboBox, is Presenter.FetchModels() being called? For that matter, does the "models" combo actually get populated with the data? Does my view subscribe to the Presenter.NotifyUser event ? And so on.

In order to help me do these kinds of tests I created two helper classes. One for testing Forms and Controls and another for testing POCO's.

Form/Control

Form derives from Control, as does every other control in WinForms. As a result, I decided to create a class containing several extension methods for Control. These extension methods allow me to do event-based stuff that is very specific to Forms and Controls. They do not apply to POCO's in which I have defined my own events, but we will talk about that in a minute. The Control extension methods include:

HandlesEvent

HandlesEvent allows me to test whether or not a particular Form event has a handler:

[Test]
public void Form_Handles_Load_Event()
{
    FormTest form = new FormTest();
    Assert.IsTrue(form.HandlesEvent("Load"));   
}

FindControl

FindControl allows me to find an instance of a control on a form. This alleviates the need to make all my controls public. Once I have the control, I can inspect values (like the Text property, for instance) and raise events on it.

[Test]
public void Can_Find_Control()
{
    FormTest form = new FormTest();
    Assert.IsNotNull(form.FindControl<Button>("btnOk"));
}

SubscribesTo

SubscribesTo lets me verify that I am handling events being raised by the specified control.

[Test]
public void Form_Handles_Button_Click_Event()
{
    FormTest form = new FormTest();
    Button btn = form.FindControl<Button>("btnOk");
    Assert.IsTrue(form.SubscribesTo(btn, "Click"));
}

RaiseEvent

RaiseEvent allows me to raise an event on either a Control or Form. In this example, I am just passing null for the event arguments.

[Test]
public void Button_Was_Clicked()
{
    FormTest form = new FormTest();
    Button b = form.FindControl<Button>("btnOk");
    b.RaiseEvent("Click", null, null);
    TextBox t = form.FindControl<TextBox>("txtResult");
    Assert.AreEqual(t.Text, "Complete");
}

 

POCO's

For POCO's I created a static class containing extension methods on System.Object. These extension methods  include SubscribesTo and RaiseEvent which work just like their analogs above, however, the underlying implementation is different.

Conclusion

To be honest, I do not know if this type of testing is valuable. These types of tests certainly don't help design in any way. However, it is interesting to see how one would determine if an event has subscribers. There is not much out there on this topic. So, from an academic point of view at least, I think the code is interesting.

You can download it here.

Tags:

Related posts

Comments

5/15/2008 1:37:08 AM

Ben Scheirman

I like how flexible this is. You can be very granular about what you require. I would not want to go this deep for all forms, however.

If you think in BDD terms, your tests should mirror a feature or acceptance criteria. The acceptance criteria isn't CustomerForm_handles_the_load_event() it's more like CustomerForm_fetches_customer_from_repository_on_load()

or probably more like...

public class when_loading_customer_form
{
public void the_customer_is_fetched_from_the_repository()
{ ... }

//more
}

Ben Scheirman us

5/29/2008 3:00:10 PM

pingback

Pingback from devpinoy.org

JP Hamilton on testing WinForms - cruizer

devpinoy.org

Add comment


(Will show your Gravatar icon)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

8/28/2008 1:19:55 AM