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.

Monday, May 30, 2011

Fun with Hibernate: Retrieving an object that should have no references

So I think I'm just going to call this a series, since there seem to be a lot of episodes of me and Hibernate not getting along.

This particular problem arose a couple weeks ago. It turned out that when you were on the website and clicked on a recording to bring up its detail page, a NullPointerException would be thrown. Great! There wasn't an obvious solution so I just logged it in our issue tracker (issue #68)

Then it looked like it only caused a problem with a specific recording (with id=4). Even more exciting was that the spot where the exception occurred was completely unrelated to the cause. Instead, it was being generated in the data layer when the query was made. Well that's never good.

Eventually we found that our Recording object, which contained a List of SongInstances, was causing the problem. One of the SongInstance objects was null. The weird part was that each SongInstance had a foreign key to a Recording. Since no-one had a reference to SongInstance, deleting a SongInstance should not have impacted anything.

Well, turns out the problem was caused by two things. First, one of our 'integration' tests tested deletion from the database (oops) and we don't have a test database yet. So it deleted one of the entries from Recording #4. The other problem was that our hibernate mapping Recording contained the following entry for mapping a list of SongInstances:

<list-index column="trackListing" base="1" />

Which caused Hibernate to retrieve the SongInstances sequentially by their "track listing." And when the test ran, it deleted the song instance that was set as track #2, causing a null pointer when Hibernate retrieved it. Oops.

So, our problem wasn't really a bug, but a side-effect of our test needing a real test database. Just one more thing to learn I guess.

In other news: we are setting a 'deadline' for feature-completeness by the end of June. Our schedule is progressing pretty well and we should be ready for The Next Step at that time. Yay!

See you next time.

Friday, May 20, 2011

Unknown Entity in Hibernate and other things

Hey everyone,

Let me get this out of the way first: I made a really careless mistake while writing the hibernate mapping files the other day (probably because I should have gone to bed) so I'm going to share it with you for my reference and so you can laugh at me.

What happened was that I had added a mapping for a class (RecordingCommentUserActivity.hbm.xml) and forgot to add a new entry to the hibernate.cfg.xml file. Oops. One of the side-effects was that Hibernate complained about requiring the fully qualified class name, which should have clued me in because you shouldn't ever need it.

The other thing that tripped me up was that a simple select query (using the full path) did not throw any errors in the log (that I saw). So I thought there was something wrong in another aspect of the project.

Of course when I tried to do something real like persist an object to the database, then it complained, giving one of those fun "Unknown Entity" exceptions.

As with a lot of problems, the hardest part was figuring out what was wrong. The fix took a few seconds. And everyone was happy.

In even more happy news, our updated security logic is nearly complete. We now track user activity and prevent duplicate voting of comments/songs/recordings/etc. I've been doing this off of another head on my branch so I can keep my main branch head free of the changes until they are more scrutinized. In the next few days I'll be switching away from security and finishing up an unimplemented voting feature for songs. That's good, because we allocated 1 week for the task on our estimates.

See you next time with more updates.

Tuesday, May 17, 2011

New directions

Hey everyone,

I'm out of school (for a bit) and working at a C# shop for the summer, so you'll probably see some C# issues crop up, although I'm still doing Java web development on the side. I might be rolling out some small WCF-based projects to learn. Maybe rewrite my chemical database project using it. That could be fun.

As for the Recordings project, we came up with a "feature complete" schedule which lists all of the tasks needing completion before we could consider pushing to production. Looks like we still have a few more months left (although we did make pessimistic estimates ... isn't that what you have to do though...)

As for myself, I'm focusing on some revamped security logic. I think I mentioned our very simplified security model in a post about Mockito, but we decided that it needed to be expanded. For example, our users are able to like/dislike (or vote up/down if you want) songs, recordings, and comments on both of those. Before we just limited the user to a certain number of votes per day, but they could still vote more than once.

This time, we're making it so that we track user activity so they can't do that until we delete the user activity entries (every X days or so). This functionality is almost done, in fact, the logic is all there. All that's missing is the Hibernate mappings and the actual data access. Right now the data layer is basically stubbed out. Mostly because writing Hibernate mapping files and data classes is very tedious and not as fun as the rest of it. But we'll get to it eventually.


Stay tune for more exciting programming related updates in the Near Future...

Tuesday, May 10, 2011

Updates.

Hey guys,

It's been a week, and I'm still going through finals. In fact, I have a physical chemistry test that's going to kill me. Expect more updates next week.