Unit Testing User Interfaces (Updated!)

Usually one of the major difficulties a developer faces when writing unit tests is how to write test code for User Interfaces. This is particularly important when we're doing Test-Driven Development.

The Visual Studio .NET IDE makes it difficult to unit test Windows Forms applications, because if we want to keep the test code decoupled from the UI code (which, in this case, is our domain code), we should keep the test code in a separate assembly and reference the assembly that contains all the forms. The IDE doesn't allow us to reference an assembly that's compiled to an .exe. Although this is only a limitation of the IDE, there is a very effective way to structure the solution so that we can develop UI code test-driven and keep the test code in an isolated assembly.

In fact, we only try to reference the Windows Application assembly, because it's where the IDE's code generator places the forms in the first place. However, we can just as easily setup a Class Library assembly and move all the forms to this assembly. All we need to do is to set up a reference from the Windows Application assembly to the Class Library assembly.

I usually setup the project structure of the solution in Visual Studio like this:

  1. Class library for the user interface
  2. Windows Application with the application launcher (Main() method)
  3. Class library for the unit tests

Some things in the previous list are worth noting. First, both project 2) and project 3) should set a reference to project 1), which is where all forms should be developed.

Second, project 2) will be the default startup project and will only have the Main() method that runs the startup form from project 1).

To develop the unit tests I use NUnitForms, which is an extension to NUnit that allows me to create an instance of a form and manipulate the controls in it.

Here's a (really) simplistic example of what a unit test might look like:

[Test]
public void TestOnButtonPressed()
{
      MyUserInterface controller = new MyUserInterface();
      controller.Show();

TextBoxTester textBox = new TextBoxTester("txtName");
      textBox["Text"] = "John Doe";

ButtonTester button = new ButtonTester("bttnHello");
      button.Click();

LabelTester label = new LabelTester("lblName");
      Assertion.AssertEquals("Hello, John Doe!", label["Text"]);
}

The test code creates an instance of the form (possible because we can reference the Class Library that has the form) as well as some objects (from NUnitForms) that I can use to control each of the form's controls (the button, the label and the textbox). It assigns the value "John Doe" to the "Text" property of the textbox control, fires the button's click event and checks if the label's "Text" property is the expected salutation string.

If I'm writing this code test-first, the previous code gets me to a red bar in NUnit, so I have to code the handler for the onClick event for the button.

private void bttnHello_Click(object sender, System.EventArgs e)
{
      lblName.Text = "Hello, " + txtName.Text + "!";
}

A quick compile and I run the test suite in NUnit and I get a green bar this time.

Of course this is a completely simplistic example, but since I can create an instance of the form and interact with its controls from my unit tests, I can implement whichever behavior is required for the user interface.

Update!
[31-03-2005]

You can download a working sample here (94Kb, zip).
Just be sure to update all the references and reference paths in the solution to the locations where you have NUnitForms installed.

Comments

  • Anonymous
    July 08, 2004
    Can you list complete code for the tester class including imports, references etc.?

    The NunitForms documentation is very lacking.

    Thanks
  • Anonymous
    July 13, 2004
    Sure.

    First reference the nunit.framework and NUnitForms assemblies (nunit.framework.dll and NUnitForms.dll).

    Then you just need to add the following uses:

    using NUnit.Framework;
    using NUnit.Extensions.Forms;

    and the namespace of your class library that has all the forms.

    After that it's just as I described above. NUnitForms doesn't have wrapper objects for all the controls (yet), but the ones that are available are the most common and should be enough for most situations.

    Anyway, here's a complete listing for a sample test class that uses NUnitForms:
    using System;
    using NUnit.Framework;
    using NUnit.Extensions.Forms;
    using DemoWinFormsApp;

    namespace NUnitFormsTester
    {
    [TestFixture]
    public class NUnitFormsTest
    {
    MyForm form;

    public NUnitFormsTest()
    {
    }

    [SetUp]
    public void SetUp()
    {
    form = new MyForm();
    form.Show();
    }

    [Test]
    public void Test()
    {

    TextBoxTester MyTextBox = new TextBoxTester("MyTextBox");
    ButtonTester MyButton = new ButtonTester("MyButton");
    LabelTester MyLabel = new LabelTester("MyLabel");

    MyTextBox.Enter("NUnitForms Test");
    MyButton.Click();
    Assertion.AssertEquals(MyLabel.Text, "NUnitForms Test");
    }
    }
    }

    hope it helps!
    Cheers!
  • Anonymous
    July 15, 2004
    The comment has been removed
  • Anonymous
    August 04, 2004
    The comment has been removed
  • Anonymous
    August 22, 2004
    Ping Back来自:blog.csdn.net