Nerdy tidbits from my life as a software engineer

Thursday, February 5, 2009

Mimics In Action

After months of anticipation, dynamics have finally made it into a stable drop of Visual Studio 2010.  And that’s given me an opportunity to play around with the new 4.0 framework and tested out my idea of using mimics to unit test classes that have non-virtual methods.

First the good news: it works! Mimics, as a concept, solve the problem of unit testing objects that lack virtual methods.  This is very exciting.  I’m still trying to play around with the API to see how to fit the concept into my mock framework, but that shouldn’t be too hard.  The important thing is that I have a proof of concept that works.

Here’s a code sample.  Say you have a WPF window (markup first, then code):

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Hello World!"
        mc:Ignorable="d"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Height="105"
        Width="181">
    <Grid>
        <CheckBox Content="Say Hello?"
                  Height="16"
                  Margin="12,12,0,0"
                  Name="mCheckBox"
                  VerticalAlignment="Top"
                  HorizontalAlignment="Left"
                  Width="120" />
        <Button Content="Go Ahead"
                Height="23"
                HorizontalAlignment="Left"
                Margin="12,34,0,0"
                Name="button1"
                VerticalAlignment="Top"
                Width="75"
                Click="OnButton1Clicked" />
    </Grid>
</Window>

using System;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            this.CheckBox = mCheckBox;
        }

        public dynamic CheckBox
        {
            get;
            set;
        } 

        /// <summary>
        /// Called when button1 is clicked.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnButton1Clicked(object sender, RoutedEventArgs e)
        {
            if (this.CheckBox.IsChecked == true)
            {
                // Test whatever ...
            }
        }

    }
}

I can now write a unit test that does this:

/// <summary>
///This is a test class for Window1Test and is intended
///to contain all Window1Test Unit Tests
///</summary>
[TestClass]
public class Window1Test
{
    /// <summary>
    ///A test for OnButton1Clicked
    ///</summary>
    [TestMethod()]
    public void OnButton1ClickedTest()
    {
        Window1_Accessor window = new Window1_Accessor(new PrivateObject(new Window1()));
        CheckBoxMimic mimic = new CheckBoxMimic();
        mimic.IsChecked = true;
        window.CheckBox = new CheckBoxMimic();
    }

    // Imagine this class was auto-generated and fit into a mock framework:
    private class CheckBoxMimic
    {
        public bool? IsChecked
        {
            get;
            set;
        }
    }

}

That’s pretty awesome, isn’t it?  Where as before .NET 4.0, the IsChecked property was un-mockable, we finally have a way to unit test GUI’s using mock frameworks.  That just tickles me happy.

So, now the bad news.  As I’ve blogged about before, the problem with dyanamics is that there is no strong-typing involved by nature, which means you won’t get a compiler error if you do something silly like invoke a bogus method on the CheckBox property.  I’ve already run into this problem.  Not having auto-complete is an issue, and there’s just no getting around the fact that you will have runtime errors if you adopt this development method in order to unit test your GUI’s.  That’s a problem.  Plus the fact that invoke dynamics is clearly less efficient than calling a compiler-bound method directly.  I can see the potential for wide-scale abuse here by terrible developers.  Let’s hope we’re not unleashing some terrible nastiness on the world.

Anyways, I have, in fact, developed a decent – though not ideal – solution to the intellisense / compile time problem.  And that solution uses preprocessor values to do some fun compile-time switching for you.  Here’s how the replaced code looks:

#if DEBUG
#define USING_MIMICS
#endif

using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            this.CheckBox = mCheckBox;
        }

#if USING_MIMICS
        public dynamic CheckBox
        {
            get;
            set;
        } 
#else
        public CheckBox CheckBox
        {
            get;
            set;
        }
#endif

        /// <summary>
        /// Called when button1 is clicked.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnButton1Clicked(object sender, RoutedEventArgs e)
        {
            if (this.CheckBox.IsChecked == true)
            {
                // ...
            }
        }

    }
}

Nobody likes preprocessor directives.  But this does solve the problem.  When writing code in the event handler, you can simply comment out the #define USING_MIMICS line at the top of the file.  That way, the properties are evaluated by their strong types.  Then, when you’re ready to test, you can uncomment the line and compile your test – which now looks like this:

#if DEBUG
#define USING_MIMICS
#endif

using WpfApplication1;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Windows;

namespace TestProject1
{   
    /// <summary>
    ///This is a test class for Window1Test and is intended
    ///to contain all Window1Test Unit Tests
    ///</summary>
    [TestClass()]
    public class Window1Test
    {

#if USING_MIMICS

        /// <summary>
        ///A test for OnButton1Clicked
        ///</summary>
        [TestMethod()]
        public void OnButton1ClickedTest()
        {
            Window1_Accessor window = new Window1_Accessor(new PrivateObject(new Window1()));
            CheckBoxMimic mimic = new CheckBoxMimic();
            mimic.IsChecked = true;
            window.CheckBox = new CheckBoxMimic();
        }

#endif

        private class CheckBoxMimic
        {
            public bool IsChecked
            {
                get;
                set;
            }
        }

    }
}

Now, to simplify the preprocessor conditionals, one thing you can do is create a new build configuration.  I made one called “Unit Testing”, where the only difference between it and debug is that it defines the USING_MIMICS constant in the build configuration in each csproj file.  That way, you can avoid putting the #if statement at the top of every file you want to use mimics on.  And the act of switching from the “Unit Testing” configuration back to Debug or Release will tell you – instantly – if your code compiles or not. 

It’s not perfect, but it’s one way to verify that dynamics will work at runtime even if the methods are invoked dynamically.

0 comments: