Thursday, May 15, 2008

MbUnit TestFixture Attribute is Now Optional

Historically, MbUnit has used a custom attribute called TestFixture to indicate that a class contains tests.

This isn't the only such attribute.  Other custom attributes, such as AssemblyFixture in MbUnit v3, denote various special kinds of fixtures.  However, TestFixture is the standard and most frequently used kind of fixture.

When James Newkirk and Brad Wilson designed xUnit.Net they omitted several features that they felt were redundant, confusing, or harmful.  TestFixture, long a staple of unit testing frameworks in .Net was one such piece of syntax that was axed: it was simply redundant.  A class is obviously a test fixture if it is not abstract and it contains suitably annotated test methods.

In a previous post I asked whether people felt that MbUnit should follow suit with xUnit.Net and either drop TestFixture altogether or make it optional.  The general sentiment seems to have fallen in the latter category.

So effective now, in MbUnit v3, TestFixture is optional.  I will publish an update to Gallio Alpha 3 with this enhancement shortly.

Examples

1. Simple fixture in C#.

public class SimpleFixture
{
    [Test]
    public void SimpleTest()
    {
        Assert.AreEqual(4, 2 + 2);
    }
}

2. Simple fixture in F#.

#light
using MbUnit.Framework

[<Test>]
let SimpleTest() = Assert.AreEqual(4, 2 + 2)

3. Data-driven fixture in C# using generic type parameters.

[Row(typeof(List<int>), typeof(int))]
[Row(typeof(Dictionary<string, int>), typeof(KeyValuePair<string, int>))]
public class DataDrivenFixture<T, S> where T : ICollection<S>, new()
{
    [Test]
    public void CountIsIncrementedWhenOneItemIsAdded()
    {
        T collection = new T();
        collection.Add(default(S));
        Assert.AreEqual(1, collection.Count);
    }
}

4. A different data-driven fixture in C# using a constructor.

public class HomestarRunnerProductTest
{
    private string productName;
    private Product product;

    [Row("Fluffy Puff Marshmallows")]
    [Row("Cheat Command O's")]
    public Fixture(string productName)
    {
        this.productName= productName;
    }

    [SetUp]
    public void BuyProduct()
    {
        product = Store.BuyProduct(productName);
    }

    [TearDown]
    public void ReturnProduct()
    {
        Store.ReturnProduct(product);
    }

    [Test]
    public void ShouldComeInAShinyBox()
    {
        Assert.IsTrue(product.HasShinyBox);
    }

    [Test]
    public void ShouldTasteSweet()
    {
        Assert.AreEqual(Flavor.Sweet, product.Flavor);
    }
}

5. Another data-driven fixture in C# using fields.

public class DataDrivenFixture
{
    [Column(OS.Windows, OS.Mac, OS.Linux)]
    public OS os;

    [Column(100, 1000, 10000)]
    public int dataSetSize;

    public void TestConfiguration()
    {
        // ... do something with os and dataSetSize
    }
}

6. A fixture represented as a static class with static test methods in C#.

public static class StatelessFixture
{
    [Test]
    public static void StatelessTest()
    {
        Assert.AreEqual(4, 2+ 2);
    }
}

7. A data-driven fixture in F# using a constructor.  (hypothetical)

I was going to demo something similar using F# but I couldn't get it to compile an attribute with object[] parameters.

I tried a bunch of variations but the only thing I could get to compile properly was a Row with an empty array.  Other attempts variously spit out errors like "error FS0191: invalid custom attribute value (not a constant): Format`5.ctor.1@[obj, obj, obj, obj, unit]`"2". "

Looks like there are bugs in the F# compiler as it does not seem to support array creation or type expressions as valid attribute values.

#light
using MbUnit.Framework

[<Row( [| 123, "123" |] )>]             <--- does not compile in F# 1.9.4.
type when_string_formatter_receives_an_integer = { value : int, formattedResult : string } with
    [<Test>]
    member f.it_should_format_the_value_as_a_string() = Assert.AreEqual(f.formattedResult, StringFormatter.Format(f.value));
end

Final Remarks

The above examples only just scratch the surface of the supported variations.  Really, you can pick and choose the style you wish to use in your tests as you like.

P.S. I also fixed the bug that prevented Jim Burger from using MbUnit effectively with F#.  (more details)

Technorati Tags: ,

1 comment:

Jim said...

Thats really quite wonderful Jeff,

Thanks for pointing out I had the .14 compiler, and making some changes to allow for this in F#.

Happily writing tests with gallio + mbunit now.