Search This Blog

Loading...

Friday, September 5, 2008

Testing Windows Form using TypeMock

Testing the GUI components are notoriously hard.  GUI components are usually not amendable to unit testing because they require user interaction. The common advice is thus to leave the UI layer as thin as possible and apply unit testing on anything that is below it. In recent years, we have seen this pattern in action with the rise of MVC or MVP pattern.

There are certainly a lot of frameworks building on top of these patterns such as ASP.NET MVC, Django Framework. Unfortunately, the idea of separation of concern isn't very much entrenched in the world of Windows Form. Microsoft Visual Studio IDE promotes the event-driven programming model that is totally running against the testability concept. As such, lots of Windows Form's code are simply fractured, downright ugly, hard to maintain and impossible to test.

The good part about TypeMock is that you can now test the previously untestable code. For so long, the testing of GUI components is relegated to unreliable test scripts that are written in a different language. Now, if you know how to use TypeMock ( or other mocking framework such as Rhino Mocks), you can automate a large part of your GUI testing. This doesn't mean that you don't need your test scripts or manual testing; you still need them, but you can now run unit tests on your GUI components, isn't that wonderful?

Here I am going to demonstrate how to use TypeMock to test some of the GUI actions. My application is simple, it consists of a property grid that displays a user's name and age. This application allows one to specify the age and the name, and save them in a XML file and load them if needed. A screenshot of this app is shown below:

A simple application, and they consist of 3 file operations, namely New, Save and Load. When you click on the New, the property grid will be refreshed. If you click Save, a standard Save file dialog box will occur, asking you where to save your file and the name of your file. If you click Load, then a standard Open file dialog will occur, asking you where to load your file.

Sounds easy? But wait till you try to unit test all those things. How your test scripts are going to handle dialog boxes popping up here and there? And how you can automate those tests?

You can't. Not without mocking framework, anyway.

In order to test this I use TypeMock. The first thing is to clearly separate out the menu action into separate class for  the ease of testing. So, I created a presenter class just for this file operation purpose.All the calling of dialog box is encapsulated inside this class:

    public class Presenter
    {
        private string CurrProjDirectory;
        private string ProjName;

        public string FullProjPath
        {
            get
            {
                return CurrProjDirectory + "\\" + ProjName + ".xml";
            }
        }

        public void Load(ref UserInfo user)
        {
            if (!SetProjectInformation(new OpenFileDialog()))
                return;

            FileStream fs = new FileStream(FullProjPath, FileMode.Open, FileAccess.Read);
            XmlSerializer serializer = new XmlSerializer(user.GetType());
            user = (UserInfo)serializer.Deserialize(fs);
            fs.Close();
        }

        public void Save(UserInfo user)
        {
            if (!SetProjectInformation(new SaveFileDialog()))
                return;


            TextWriter writeFilStream = new StreamWriter(FullProjPath);
            XmlSerializer serializer = new XmlSerializer(user.GetType());
            serializer.Serialize(writeFilStream, user);
          
            writeFilStream.Close();
        }


        private bool SetProjectInformation(FileDialog fbd)
        {
            fbd.DefaultExt = ".xml";
            fbd.Filter = "XML (*.xml)|*.xml";
            if (fbd.ShowDialog() == DialogResult.OK)
            {
                CurrProjDirectory = Path.GetDirectoryName(fbd.FileName);
                ProjName = Path.GetFileNameWithoutExtension(fbd.FileName);
                return true;
            }

            return false;
        }
    }

You can always refer to the TypeMock and MSDN documentation if the above code looks alien to you.

Having this presenter already, we can now test the save and load operation, for simplicity sake I am just going to write a single test method. Following my rule on code comment, I am not going to explain the code and hope that it is self-explanatory:


    [TestFixture, ClearMocks]
    public class PresenterTest
    {
        [Test, VerifyMocks]
        public void TestSaveLoad()
        {
            string filePath = @"C:\test.xml";
            using (RecordExpectations recorder = RecorderManager.StartRecording())
            {
                OpenFileDialog ofd = new OpenFileDialog();
                recorder.ExpectAndReturn(ofd.ShowDialog(), DialogResult.OK).RepeatAlways();
                recorder.ExpectAndReturn(ofd.FileName, filePath).RepeatAlways();
            }

            using (RecordExpectations recorder = RecorderManager.StartRecording())
            {
                SaveFileDialog ofd = new SaveFileDialog();
                recorder.ExpectAndReturn(ofd.ShowDialog(), DialogResult.OK).RepeatAlways();
                recorder.ExpectAndReturn(ofd.FileName, filePath).RepeatAlways();
            }

            UserInfo user = new UserInfo();
            user.Age = 10;
            user.Name = "no name";
            Presenter presenter = new Presenter();
            presenter.Save(user);
            Assert.IsTrue(File.Exists(filePath));

            UserInfo userNew = new UserInfo();
            presenter.Load(ref userNew);
            Assert.AreEqual(user.Age, userNew.Age);
            Assert.AreEqual(user.Name, userNew.Name);

        }

    }


If you can get this test pass, you are are pretty sure of the correctness of your code. The final step is to call the appropriate presenter methods when the menu is clicked:


        private void TypeMocForm_Load(object sender, EventArgs e)
        {
            presenter = new Presenter();
            user = new UserInfo();
            propertyGrid1.SelectedObject = user;
        }

        private void newToolStripMenuItem_Click(object sender, EventArgs e)
        {
            user = new UserInfo();
            propertyGrid1.SelectedObject = user;
        }

        private void loadToolStripMenuItem_Click(object sender, EventArgs e)
        {
           presenter.Load(ref user);
           propertyGrid1.SelectedObject = user;
        
        }

        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            presenter.Save(user);
        }


You can test whether the application works as expected or not. The full source code is available at here

Testing Windows Form is possible, as long as you know how to structure your code and use appropriate mocking tools.

Any questions? I will write another post to discuss further on this topic. If you have any questions, let me know so that I can incorporate them in the next discussion!

3 comments:

Arild said...

Tests like that tend to be a little overspecified and brittle. Have you ever checked out TestAutomation FX?

Soon Hui said...

Arild, thanks for introducing me the TestAutomation FX!

I will post my thoughts in the next post.

Kiquenet said...

any update about it in 2014, for unit testing and Windows Forms ?