September, 2006

Saturday, September 23rd, 2006

Moving to WiX

NUnit has finally gotten rid of it’s Visual Studio Install projects in favor of WiX

VS projects support only a simple, single-feature install. What’s more, if you outgrow your install project, you can’t simply migrate it to another tool. You have to start over with the new tool. It would be cool if VS could use WiX syntax as it’s internal format, but I suspect that’s not likely to happen since it would tie future development of the two together in some ways that would probably discomfit both WiX and Visual Studio. But an open source converter from the vdproj format to WiX would definitely be a cool thing!

One side effect of this change is that users of C# Express can now load and build the entire NUnit solution. I’m hoping this will gain us a few volunteers.

Jamie Cansdale – of NUnit Addin and TD.Net fame – did the initial implementation of the WiX scripts and I made additional changes and figured out how to use the the NAnt tasks so it could be made part of our standard build.

It’s unfortunate that the WiX tasks for NAnt come without documentation. By reading the source, I was able to construct the commands I listed below, which I’m posting in the hope that they will be of help to somebody.

<candle out="${wix.work.dir}/" exedir="${wix.dir}">
  <defines>
    <define name="ProductVersion" value="${package.version}" />
  </defines>
  <sources basedir="${project.install.dir}">
    <nclude name="bin.wxs" />
    <include name="nunit-gui.wxs" />
    <include name="doc.wxs" />
    <include name="tests.wxs" />
    <include name="samples.wxs" />
    <include name="NUnit.wxs" />
  </sources>
</candle>

<light exedir="${wix.dir}"
  out="${project.package.dir}/${msi.file.name}" 
  locfile="${wix.dir}/WixUI_en-us.wxl">
  <sources>
    <include name="${wix.work.dir}/NUnit.wixobj" />
    <include name="${wix.work.dir}/bin.wixobj" />
    <include name="${wix.work.dir}/nunit-gui.wixobj" />
    <include name="${wix.work.dir}/doc.wixobj" />
    <include name="${wix.work.dir}/samples.wixobj" />
    <include name="${wix.work.dir}/tests.wixobj" />
    <include name="${wix.dir}/wixui.wixlib" />
  </sources>
</light>

You’ll find our WiX scripts in the latest version of the NUnit source on SourceForge.

Charlie

Friday, September 1st, 2006

More On Syntax: Expected Exceptions

Sometimes you expect an exception to be thrown by a method. So, of course, you want a test for that. NUnit provides the ExpectedExceptionAttribute for that purpose. It has a bit of history…

First, it was a list of exception types. That’s right, in NUnit 1.x, you could specify multiple expected exceptions. At some point, somebody thought “Wait, shouldn’t you know what you’re testing for?” and it was reduced in NUnit 2.0 to a single exception type.

[Test, ExpectedException(typeof( MyException ) )]

Then there was the message. Folks wanted to check for a single exception, so a constructor argument was added that allowed you to specify the message.

[Test, ExpectedException(typeof( MyException ), "My special message" )]

But some messages are pretty long, so you might want to specify a substring…

[Test, ExpectedException(typeof( MyException ), "special", MessageMatch.Contains )]

This doesn’t even get into specifying the exception by name rather than type, using regular expressions, providing a custom user message in case of failure,… but I’m sure you get the idea. The ExpectedExceptionAttribute has been overloaded with options, but people keep asking for more, like checking the value of specific properties on a specific type of exception.

Working on NUnitLite has given me the chance to look at this with fresh eyes. The heart of the problem is that there is only so much you can express between the square brackets when you use an attribute. Properties may only be of certain types and only so much fits on the line in a readable way. On the other hand, it’s reasonably simple to check various properties of an exception in a catch block – or in any other code for that matter.

For NUnitLite, I’m trying a simpler approach. First of all, you can simply specify ExpectedException without any arguments…

[Test, ExpectedException]

That means you expect some sort of exception – but you aren’t saying what. I know that this isn’t a very good testing practice by itself, but there are uses for it, as we’ll soon see. Normally, you’ll want to specify an exception type…

[Test, ExpectedException( typeof(MyException) )]

This works slightly differently from NUnit. The test will succeed if MyException or any exception that inherits from it is thrown – NUnit requires the exact type.

Now suppose you want to verify that MyException is thrown and has an particular message and that a certain property it supports has been set to a certain value. If your test class implements the IExpectException interface, you are able to define a handler to validate the exception. Here’s the interface…

public interface IExpectException
{
        void HandleException( Exception ex );
}

Here’s an implementation to deal with the hypothetical example

public void HandleException( Exception ex )
{
        Assert.That( ex.Message, Is.EqualTo( "my special message" );
        MyException mex = (MyException) ex;
        Assert.That( mex.Param == "xxxxx" );
}

As I’ve experimented with this, I have found that it was rare for me to need more than one exception handler in a class. I can usually generalize one method sufficiently to make it work for all my needs. But, in some cases, there is a need for a different handler. In that case, you can specify the name of the handler method as an additional property of ExpectedException…

[Test, ExpectedException(Handler="AlternateHandler")]
or
[Test, ExpectedException(typeof(MyException), Handler="AlternateHandler")]

The method must exist and have the correct signature or a run-time error will be thrown.

This is as far as I’ve taken it. I can think of other ways to expand (read complicate) it, but I’m not doing that. Users can write anything they want in the handler and if something turns out to be needed repeatedly we can think about removing the duplication later.

Charlie