Sunday, June 19, 2011

Writing and testing a 2007 MS Word Add-in using NUnit, Moq, and the MVP pattern

Hey everyone,

Today I want to show you an approach to writing a testable UI. To do this I'm going to write a 2007 Microsoft Word Add-in using Microsoft's Visual Studio Tools for Office. The goal is to write an application level add-in and test it using NUnit and Moq.

 I'm going to design it around the Model-View-Presenter pattern: We'll have two interfaces: An IView, which will be an Office ribbon and contain no logic. Then we'll  have an IPresenter, which will handle all of the "UI logic" such as taking actions based on user dialog results etc.

Our Presenter won't actually have any domain logic in it: we'll be delegating that to another class (I'll explain that in a minute). This means that when we test it, we'll be doing behavior testing instead of testing for object states. This also means we'll be using Moq to setup our Mock domain objects and verifying that calls are made to them.

Of course it's important to note that this kind of testing involves some coupling between your tests and the implementation of the class under test, but I think it's acceptable since the Presenter is behavior based.

So what kind of add-in will we be doing? Well at first I was going to be boring and use a very contrived example. But then I came up with a nice domain-specific idea.

I figured why not do something chemistry related? That's always fun. So we're going to make an addition to the Word ribbon that allows the user to enter either a chemical formula or an entire reaction equation. When the user clicks the "Action" button, the add-in will either render the equation as an image and insert it, or if the user enters a single compound it will insert the molar mass at the cursor position.

Here's the user interface:


Don't worry about the actual logic behind rendering and molar mass calculation. I've already taken care of that using my chemical database program I wrote last year. Now of course I think the code is crap, but it still works and that's the main thing.

So let's get started by defining our interfaces. Our IPresenter will only contain a single method: Action() which returns void and expects no parameters. Our Ribbon will implement IView. To start with, we'll give IView two properties: MolarMass and EquationText:


IView.cs:
namespace Chemistry.UI
{
    public interface IView
    {
        double MolarMass
        {
            set;
        }
        string EquationText
        {
            get; 
        }
    }
}
IPresenter.cs:
namespace Chemistry.UI
{
    public interface IPresenter
    {
        void Action(); 
    }
}

In our first iteration we'll simply assume that the user-entered text is a compound and we want to calculate the molar mass of it. Let's write a simple test that will confirm our IPresenter implementation makes a call to our domain object (an instance of IFormulaParser):

Presenter.cs:
namespace Chemistry.UI
{
    public class Presenter : IPresenter
    {
        public void Action()
        {
            throw new NotImplementedException();
        }
    }
}
IFormulaParser.cs:
namespace Chemistry.UI
{
    public interface IFormulaParser
    {
        double MolarMass(string compound); 
    }
}
And now our test file, PresenterTests.cs:

using NUnit.Framework;
using Moq;
using Chemistry.UI; 
namespace Tests
{
    public class PresenterTests
    {
        private IPresenter _presenter;
        private Mock<IFormulaParser> _formulaParser; 

        [SetUp] 
        public void SetUp()
        {
            _presenter = new Presenter();
            _formulaParser = new Mock<IFormulaParser>();            
        }

        [TestCase] 
        public void Action_UserEntersCompound_CallsMolarMassMethod()
        {
            _presenter.Action(); 
            _formulaParser.Verify(parser => parser.MolarMass("O2")); 
        }
    }
}

So in our test class we initialize a new Presenter object and a mock FormulaParser. For now we've set the IFormulaPresenter to return a value of 32 when MolarMass("O2") is called. In our test case, we make sure that our FormulaParser is called correctly. Of course the test fails since Presenter doesn't do anything. Let's change it so that it uses the text entered by the user instead our hard-coded value.

using NUnit.Framework;
using Moq;
using Chemistry.UI; 
namespace Tests
{
    public class PresenterTests
    {
        private IPresenter _presenter;
        private Mock<IFormulaParser> _formulaParser;
        private Mock<IView> _view; 

        [SetUp] 
        public void SetUp()
        {
            _presenter = new Presenter();
            _formulaParser = new Mock<IFormulaParser>();            
            _view = new Mock<IView>(); 
        }

        [TestCase("O2",32.0)] 
        [TestCase("H2", 2.0)]
        [TestCase("C6H6",78.0)]
        public void Action_UserEntersCompound_CallsMolarMassMethod(string compound, double molarMass)
        {
            _view.Setup(view => view.EquationText).Returns(compound);
            _formulaParser.Setup(parser => parser.MolarMass(compound)).Returns(molarMass);                 

            _presenter.Action(); 

            _formulaParser.Verify(parser => parser.MolarMass(compound)); 
        }
    }
}


Now we've done a couple things. First, we've created a new mock IView. We also changed our test to accept parameters and set up the view's EquationText property with the compound string. Finally, we check again to make sure the presenter calls the FormulaParser.MolarMass method. Now let's make the Presenter pass the test.

public class Presenter : IPresenter
{
 private IFormulaParser _formulaParser;
 private IView _view;

 public Presenter(IView view)
 {
  _view = view;
  _formulaParser = new FormulaParser(); 
 }

 public void Action()
 {
  _view.MolarMass = _formulaParser.MolarMass(_view.EquationText); 
 }
}

Alright, so now the Presenter is using the IView's equation text property, calculating the molar mass, and setting the IView's MolarMass property to the result. Let's also update the test cases to verify that View's MolarMass property is also set:

[SetUp] 
public void SetUp()
{
 _view = new Mock<IView>();
 _formulaParser = new Mock<IFormulaParser>();                  
 _presenter = new Presenter(_formulaParser.Object, _view.Object);                  
}

[TestCase("O2",32.0)] 
[TestCase("H2", 2.0)]
[TestCase("C6H6",78.0)]
public void Action_UserEntersCompound_CallsMolarMassMethod(string compound, double molarMass)
{
 _view.Setup(view => view.EquationText).Returns(compound);
 _formulaParser.Setup(parser => parser.MolarMass(compound)).Returns(molarMass);  
              
 _presenter.Action(); 

 _formulaParser.Verify(parser => parser.MolarMass(compound));
 _view.VerifySet(view => view.MolarMass = molarMass); 
}

At this point, our test passes. And at this point if we implement IView in our Ribbon object, everything should work. So let's proceed and implement rendering equations as well. Let's start by writing a test to make sure our rendering object is called. To do thish we need to add our IEquationRenderer interface, which has a reference to System.Drawing:

public interface IEquationRenderer
{
 Image RenderEquation(string equation); 
}

And modify the Presenter constructor:

public Presenter(IEquationRenderer equationRenderer, IFormulaParser formulaParser,  IView view)
{
 _view = view;
 _formulaParser = formulaParser;
 _equationRenderer = equationRenderer; 
}

Finally add our test case:

[TestCase("H2 + O2 ----> H2O")]
public void Action_UserEntersEquation_CallsEquationRenderer(string equation)
{                        
 _view.Setup(view => view.EquationText).Returns(equation);            
 _presenter.Action();
 _equationRenderer.Verify(renderer => renderer.RenderEquation(equation));
 _view.VerifySet(view => view.EquationImage = It.IsAny<Image>()); 
}

An important note here: I wasn't able to mock the Image interface that we're passing around, so we're verifying that the IView gets its EquationImage property set to "any" Image object instead of a dummy Image that we would pass around normally.

If we make this test pass by just adding a call to our EquationRenderer, we could also still be calling the molar mass calculation which we don't want. So let's add another test that ensures that when we parse an equation, we don't try a molar mass calculation:

[TestCase("H2 + O2 ----> H2O")]
public void Action_UserEntersEquation_DoesNotTryMolarMassCalculation(string equation)
{
 _view.Setup(view => view.EquationText).Returns(equation);
 _presenter.Action();
 _formulaParser.Verify(parser => parser.MolarMass(It.IsAny<string>()), Times.Never()); 
}

This test will fail because we need some conditional logic to differentiate compounds and equations. Let's do this by adding a method to IFormulaParser:

bool IsEquation(string text); 

Let's set this up in our equation tests to return true by adding this setup line to our two test cases:

_formulaParser.Setup(parser => parser.IsEquation(equation)).Returns(true); 

The actual implementation will just check for the presence of "---->," but that could always change. Now we can modify the Presenter to take this into account:

public void Action()
{
 if (_formulaParser.IsEquation(_view.EquationText))
 {
  _view.EquationImage = _equationRenderer.RenderEquation(_view.EquationText); 
 }
 else
 {
  _view.MolarMass = _formulaParser.MolarMass(_view.EquationText);
 }            
}

And now our tests pass. At this point, the rest of the work is implementation details. Here's an example of the final result:

The important thing to note with this approach is that we've created an extremely testable UI layer. Since all of the logic is in the Presenter class, we can test all aspects of the UI. This becomes even more exciting when your UI logic involves the user making decisions by answering prompts and responding to dialogs. You could for example wrap a call to MessageBox.Show in an IView DisplayMessage() method that returns a DialogResult. This would allow you to test the Presenter and just mock the IView.

Anyway, just for reference, the full source code of this add-in is on my website. Apologies in advance for the weird project structure, the Test project is under the "Tests" folder. The solution uses Visual Studio 2010.

Sunday, June 12, 2011

Updates, progress, what's coming up...

Hey everyone. Not much for you today. I would like to say what kind of topics will be appearing on here in the next few weeks though.

First: I've been working on an MS Office add-in in C# and would like to share my experience unit testing it with NUnit and Moq.

Then, the Recordings project is moving along well. We're on schedule to have all "core features" in by the end of the month. At that point we won't be making any new feature additions -- just working on testing/bug fixes/etc. and also the build process.

We've decided to set up a build server using Hudson to build/test/deploy our web application -- so I'm sure of course there will be plenty of problems that will arise for us to resolve. I for one am pretty excited about automation -- we'll be doing a clean check out from our production branch in Mercurial, running our unit/integration tests, minifying our javascript/css, and I'm sure all kinds of fun tasks. By the end I'll write about our build process (for reference -- and if anyone finds it useful that's always good).

Apologies for nothing more concrete (ie: some code or a problem or something).But hey, I'm sure some stuff will come up!

Sunday, June 5, 2011

Labeling the top 5 results on a graph with jqPlot

Hey everyone,


As you can guess by the title, we're back with more on jqPlot! Last time I talked about jqPlot I had wanted to use tick marks in the Highlighter plugin for axes where the data was non-numerical. The whole reason of using the highlighter in the first place was because some of our graphs have large numbers of data points, so we have to hide the axes labels or it looks terrible.

A large data set (129 points). 
So we hide the axes labels and it looks better, but now you can't tell what any of the points are. We added the highlighter plugin so that at least when you hover over a data point, you see its value. That's helpful, but what if you wanted to, for example, print this graph out?

We decided to label the graph for the points with the top X (where X=5 at the moment) y-values on the graph. In the end we get this:

Labeling the top 5 results looks pretty nice. 
Now if you print it out, at least you see the most important values on the graph.

The Plugin

So in order to accomplish this I started from an existing plugin (Highlighter since that's what I'm familiar with). I'm going to explain how I changed the highlighter plugin to accomplish what I needed, but you don't have to start with it at all. There are a couple things we have to change to make it work:

  • Modify the postPlotDraw hook so that it sets up labels for the number of points we want to draw the label for.
  • Make a call to our own function that will determine which points to draw labels
  • Modify the showTooltip function so that it uses more than one label 
None of these are really hard, so let's just start with the first step.

The first thing is to add two new variables to the plugin object itself:

$.jqplot.TopResultDisplay = function(options) {
        this._tooltipElements = []; 
        this.numberOfPoints = 5; 

        /*   the rest of the options .... */ 
};

TopResultDisplay is the name of the plugin. _tooltipElements is empty right now but will contain a number of div elements that represent the labels. numberOfPoints determines how many top points we want to label.

Now we need to modify the postPlotDraw() function so that it creates those divs. It will also make a call to our function that will eventually draw the labels. After modifying it, the postPlotDraw() function looks like this:

// called within context of plot
    // create a canvas which we can draw on.
    // insert it before the eventCanvas, so eventCanvas will still capture events.
    $.jqplot.TopResultDisplay.postPlotDraw = function() {
        this.plugins.TopResultDisplay.highlightCanvas = new $.jqplot.GenericCanvas();
        
        this.eventCanvas._elem.before(this.plugins.TopResultDisplay.highlightCanvas.createElement(this._gridPadding, 'jqplot-highlight-canvas', this._plotDimensions));
        this.plugins.TopResultDisplay.highlightCanvas.setContext();
        
        var p = this.plugins.TopResultDisplay;
        
        for(i =0;i<p.numberOfPoints;i++)
        {
            elem = $('<div class="jqplot-highlighter-tooltip" style="position:absolute;display:none"></div>');
            p._tooltipElements[i] = elem;
            this.eventCanvas._elem.before(elem);
        }        
        
        drawLabelsForPoints(this); 
    }; 

The changes I made start at line 10: we simply create a number of div elements and insert them on the _tooltipElements array.

Then we add a call to drawLabelsForPoints(this) which will be our label drawing function that takes in a plot object.

Now let's write that function. When I was changing the Highlighter plugin this function was originally handleMove, but I ended up changing all of the functionality. The function itself is not that exciting:

function drawLabelsForPoints(plot) 
    {
        var hl = plot.plugins.TopResultDisplay; 
        
        var sortedData = []; 
                
        for(i =0;i<plot.series[0].data.length;i++)
        {            
            var newPoint = new Object(); 
            newPoint.data = plot.series[0].data[i];
            newPoint.seriesIndex = 0; 
            newPoint.gridData = plot.series[0].gridData[i];             
                        
            sortedData.push(newPoint);                                        
        }        
        
        sortedData.sort(function(a, b) { return b.data[1] - a.data[1]; }); 
                
        for(i=0;i<hl.numberOfPoints;i++)
        {            
            showTooltip(plot, plot.series[0], sortedData[i]);
        }    
    }

First we create a new array called sortedData. Then we fill this array with all of our unsorted points, but we use the format that showTooltip of Highlighter expects. The data variable refers to the actual data array of the form [x,y]. seriesIndex is the series that the point is on, and gridData refers to the points x/y pixel coordinates on the canvas. You might notice that I'm only using one series here since all of our graphs have only one series. You could however modify it to support more than one if needed.

After filling the array, we sort in place using a function that sorts the data in descending order based on the y-value. Finally, we call showTooltip on the top results with the number of points that was specified.

The next step is to modify showTooltip to use more than one label element. To do this I changed a small part of the showTooltip function at the top to look like this:

    var hl = plot.plugins.TopResultDisplay;
        
        if(!hl.enabled) return;        
        var elem = hl._tooltipElements.splice(0, 1)[0];         
        if(!elem) return; 
        
        if (hl.useAxesFormatters) {
        /* .. . the rest of the function */

The important line is line 4, where we make the call to _tooltipElements.splice. This removes one element from the array of divs and returns it, which means that as long as it returns non-null we still have labels to use. After that the rest of the code remains the same and references the elem object to draw on.

The very last step is to remove the line

$.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
from the top of the code. This line wires up the handleMove method to be called whenever the mouse is moved over the canvas.

After that, we're done with changing the code. All that's left is to enable the plugin. We do that just as any other plugin. Below is the configuration for the plugin as we have it on our graphs page.

TopResultDisplay:
{
 tooltipAxes: 'xy',
 useAxesFormatters:true,
 tooltipLocation: 'n', 
 numberOfPoints: 5
}, 

And the results again for reference:

So that's it! If you have any questions/comments/etc. feel free to leave a comment.