[ptsefton.com] | [CV & Bio]

Unit testing for VBA in 2 minutes

2007-03-13

Here's the story of how I came to write a 5 line unit test framework for use in Microsoft Word macros. OK so it's not a framework, but it's very useful.

It's been a long time since I've written more than a few lines of VBA. That's Visual Basic for Applications. It's not exactly a pleasant experience, rather like communicating with the computer via grunts. String manipulation in particular is a real pain after using languages with regular expressions built-in. I guess these days in the Windows world you can use the .NET libraries with your choice of language; C#, VB, even Python. But for code that will work across lots of version of Word and on the Mac it's still VBA.

At the ICE project we're attempting to improve our user interface, which means putting in some new toolbar buttons to apply style features. Want a bulleted list? Hit the bullet button. Want to increase or decrease the level of indent on a list item? Or promote or demote a heading? Use the new array of buttons.

The buttons will look something like:

* 1. (a) (A) (i) (I) | <- -> | [P] [H] [B] [I]

Thats demote ->, promote <-, bullet, a few kinds of lists, H for heading and Bold and Italic. Obviously the buttons will look nicer than those shown above. I've left off blockquotes and a few other things.

Anyway, to make all this happen there needs to be some fairly smart code behind the scenes. What should happen if I press the 'promote' or 'de-indent' left-pointing button when I'm typing an ordinary paragraph like this? Nothing much. How about the 'demote' button? Should that give me a blockquote? I think so, but maybe a bulleted list. There are a lot of permutations to think about.

Over the last few years I've become used to taking a test-first approach to this kind of problem. We can sit down and map out the behaviour we'd like and express it as a set of tests that both specify and document the code we're writing.

Enter the unit test framework for VBA. Only there doesn't seem to be one.

So I wrote this five line subroutine.

Sub AssertEqual(name, string1, string2)

If string1 <> string2 Then

MsgBox ("Failed: " + name + ": '" + string1 + "' <> '" + string2 + "'")

End If

End Sub

All this does is take a test name and two things that are supposed to be the same (they're called strings in my code but actually they're variants which could contain anything, silly me) and compare them. If they don't match then it throws up a message box. That's all.

Then all I had to do was write a test subroutine. Here's a few sample tests:

Sub tests()

Call AssertEqual("Don't Indent li1b after p", Indent("li1b", "p"), "li1b")

Call AssertEqual("Indent li1b after li1b", Indent("li1b", "li1b"), "li2b")

Call AssertEqual("Indent p after li3b", Indent("p", "li3b"), "li3p")

Call AssertEqual("Indent p after bq2", Indent("p", "bq2"), "bq2")

Call AssertEqual("Indent p after dt1", Indent("p", "dt1"), "dd1")

Call AssertEqual("Indent p after p", Indent("p", "p"), "bq1")

Call AssertEqual("Indent li5b", Indent("li5b", "li5b"), "li5b")

End Sub

Take one line of this:

Call AssertEqual("Indent li1b after li1b", Indent("li1b", "li1b"), "li2b")

This is saying, when I'm currently in style li1b, a first level bulleted list, and the previous style is the same when I press the demote -> button I want the result to to be a second level bulleted list

The idea is to write the tests before the Indent() function; I have started the job in an iterative way. Add a few tests, write a bit more code. You can see yesterday's version of the code on the ICE website; I have built on stuff written by other USQ staff, and they'll go on to do more.

I didn't bother with all the usual unit test framework stuff, counting tests, reporting on which ones worked and didn't and so on, just an annoying message box that pops up when something fails. This a great incentive to make things work, cos clicking the box is really annoying.