Tuesday, January 04, 2005

Mike Gunderloy has posted a nice introductory article on using TD.NET and MbUnit in Visual Studio. Check it out at http://www.developer.com/net/net/article.php/3453121

This article is really about TD.NET as Mike emphasis on one of the zillions of feature of TD.NET. There are a lot of very nice feature in TD.NET, just take a moment to discover them.

Going beyond Mike sample with MbUnit

Mike illustrates the article with a little class that stores an integer and returns information such as IsPositive, etc... In the article, Mike uses the canonical fixture. Let's if we can do better. (disclaimer: my VB is really bad)

RowFixture

Typically, you want to test this for 'interresting' values such 0,-1,1,MaxInt,etc... This example really fits well into the example of the RowFixture:

Imports System
Imports MbUnit.Core.Framework
Imports MbUnit.Framework

<TestFixture()> _
Public Class NumberTests
    ...

    <RowTest()> _
    <Row(5,FALSE)> _
    <Row(8,TRUE)> _
    Public Sub TestIsEven(int value, Boolean isEven)
        ' Make sure even numbers are property identified
        Dim num As New Number
        num.Value = value;
        Assert.AreEqual(isEven, num.IsEven)
    End Sub

End Class

This will create a test case for each RowAttribute. Better readibabilty, better test separation. Now can we do better ?

Combinatorial test

Let's try another approach. We could create a table of 'interresting' number with their properties (is positive, is even). Once this is done, we can feed this table to combinatorial tests.

<TestFixture()> _
Public Class NumberTests

    Public Class NumberRow
        Public int Value
        Public bool IsPositive
        Public bool IsEven

        Public New(int value, bool isPositive, bool isEven)
            Me.Value = value
            Me.IsPositive = IsPositive
            Me.IsEven = isEven
        End New
    End Class

    <Factory()> _    
    Public Function Numbers() as Array Of NumberRow
        Dim numbers As New Array Of NumberRow[4];

        numbers[0] = New NumberRow(-1, False, False)
        numbers[1] = New NumberRow(0, False, True)
        numbers[2] = New NumberRow(1, True, False)
        numbers[3] = New NumberRow(2, True, True)

        Return numbers
    End Function
    
    <CombinatorialTest()> _
    Public Sub TestIsEven(<UsingFactories("NUMBERS")> _ NumberRow row)
        ' Make sure even numbers are property identified
        Dim num As New Number
        num.Value = row.Value;
        Assert.AreEqual(row.IsEven, num.IsEven)
    End Sub

End Class

This will create 4 test cases, one per element of the array returned by Numbers. Moreover, we could reuse Numbers in another test to verify IsPositive.

[Update]
If you plan to reuse the factory, you can extract it into a separate class and pass the facotry type of the UsingFactories parameter.

Public Class NumberRow
    Public int Value
    Public bool IsPositive
    Public bool IsEven

    Public New(int value, bool isPositive, bool isEven)
        Me.Value = value
        Me.IsPositive = IsPositive
        Me.IsEven = isEven
    End New
End Class

Public Class NumberFactory
    <Factory()> _    
    Public Function Numbers() as Array Of NumberRow
        Dim numbers As New Array Of NumberRow[4];

        numbers[0] = New NumberRow(-1, False, False)
        numbers[1] = New NumberRow(0, False, True)
        numbers[2] = New NumberRow(1, True, False)
        numbers[3] = New NumberRow(2, True, True)

        Return numbers
    End Function
End Class

<TestFixture()> _
Public Class NumberTests
    
    <CombinatorialTest()> _
    Public Sub TestIsEven(<UsingFactories(CType(NumberFactory))> _ NumberRow row)
        ' Make sure even numbers are property identified
        Dim num As New Number
        num.Value = row.Value;
        Assert.AreEqual(row.IsEven, num.IsEven)
    End Sub

End Class
posted on Tuesday, January 04, 2005 2:48:00 PM UTC  #    Comments [11]
Monday, June 06, 2005 4:47:23 PM UTC
Hey there again Jonathan...
<br>
<br>I was wondering if there is somewhere where all of the different attribute tags of <a title="MbUnit, Generating Unit Testing and Model Based Testing Framework for .NET Framework" href="http://mbunit.tigris.org" target="_blank">MbUnit</a> are documented.
<br>
<br>I'm trying to auto generate some unit tests using <a title="CodeSmith" href="http://www.ericjsmith.net/codesmith/" target="_blank">CodeSmith</a> and would like to encorporate lots of the nifty <a title="MbUnit, Generating Unit Testing and Model Based Testing Framework for .NET Framework" href="http://mbunit.tigris.org" target="_blank">MbUnit</a> tags. specifically the database roll back ones (I think nunit has this also) and the row and combinatorial ones you have listed.
<br>
<br>Also I have some questions on how to maybe make testing around database constraints a little easier. Preferrable w/o using mock objects. I'd like to know if you have any ideas. if ya do please email me.
<br>
<br>jayrod84@gmail.com
Jerrad Anderson
Monday, June 06, 2005 4:47:25 PM UTC
Jonathan -
<br>
<br>Very cool stuff and very timely as well. I'm working on something right now where I'll be able to make use of the combinatorial testing you describe.
<br>
<br>One question - in your final example you have everything in a single class. I tried pulling the method marked with the Factory attribute into a separate VB code module so that I use the same factory in different test fixtures but <a title="MbUnit, Generating Unit Testing and Model Based Testing Framework for .NET Framework" href="http://mbunit.tigris.org" target="_blank">MbUnit</a> failed with a message about a parent fixture failing.
<br>
<br>Must the factory reside in the same test fixture?
<br>
<br>Thanks,
<br>
<br>Mike H.
Mike Harges
Monday, June 06, 2005 4:47:25 PM UTC
Jerrad,
<br>
<br>Make sure you take a peek at the <a title="MbUnit, Generating Unit Testing and Model Based Testing Framework for .NET Framework" href="http://mbunit.tigris.org" target="_blank">MbUnit</a> wiki for documentation of the available attributes:
<br><a target="_new" href="http://testdriven.net/wiki/default.aspx/MyWiki.MbUnitFeatures">http://testdriven.net/wiki/default.aspx/MyWiki.MbUnitFeatures</a>
<br>
<br>Also take a look at my database populator framework posts on the blog (google for that). TestFu contains a framework that can generate data that is compatible with the database constraint.
<br>
<br>Mike,
<br>
<br>Yes you can definitely separate the factory from the fixture. I've updated the blog entry to show that.
<br>
Jonathan de Halleux
Monday, June 06, 2005 4:47:25 PM UTC
Jonathan -
<br>
<br>Your updated example was just what I needed. Thanks for the quick reply.
Mike Harges
Monday, June 06, 2005 4:47:25 PM UTC
I tried to write RowTest and used exactly the same syntax as in example above. I got the compilation error:
<br>Attribute specifier is not a complete statement...
<br>I also got error message &quot;A parent fixture failed&quot; when running combinatorial test. Could you help me with this problem?
Anna
Monday, June 06, 2005 4:47:25 PM UTC
VB.NET is certainly not my cup of tea. Here's a verified and compiled sample
<br>
<br>&lt;pre&gt;
<br>Imports System
<br>Imports <a title="MbUnit, Generating Unit Testing and Model Based Testing Framework for .NET Framework" href="http://mbunit.tigris.org" target="_blank">MbUnit</a>.Framework
<br>
<br>&lt;TestFixture()&gt; _
<br>Public Class NumberTests
<br> &lt;RowTest()&gt; _
<br> &lt;Row(5, False)&gt; _
<br> &lt;Row(8, True)&gt; _
<br> Public Sub TestIsEven(ByVal value As Integer, ByVal isEven As Boolean)
<br> ' Make sure even numbers are property identified
<br> Dim num As New Number
<br> num.Value = value
<br> Assert.AreEqual(isEven, num.IsEven)
<br> End Sub
<br>
<br>End Class
<br>
<br>Public Class NumberRow
<br> Public Value As Integer
<br> Public IsPositive As Boolean
<br> Public IsEven As Boolean
<br>
<br> Public Sub New(ByVal value As Integer, ByVal isPositive As Boolean, ByVal isEven As Boolean)
<br> Me.Value = value
<br> Me.IsPositive = IsPositive
<br> Me.IsEven = isEven
<br> End Sub
<br>End Class
<br>
<br>Public Class NumberFactory
<br> &lt;Factory()&gt; _
<br> Public Function Numbers() As NumberRow()
<br> Dim nb(3) As NumberRow
<br>
<br> nb.SetValue(New NumberRow(-1, False, False), 0)
<br> nb.SetValue(New NumberRow(0, False, True), 1)
<br> nb.SetValue(New NumberRow(1, True, False), 2)
<br> nb.SetValue(New NumberRow(2, True, True), 3)
<br>
<br> Return nb
<br> End Function
<br>End Class
<br>
<br>&lt;TestFixture()&gt; _
<br>Public Class NumberCombinatorialTests
<br>
<br> &lt;CombinatorialTest()&gt; _
<br> Public Sub TestIsEven(&lt;UsingFactories(GetType(NumberFactory))&gt; ByVal row As NumberRow)
<br> ' Make sure even numbers are property identified
<br> Dim num As New Number
<br> num.Value = row.Value
<br> Assert.AreEqual(row.IsEven, num.IsEven)
<br> End Sub
<br>
<br>End Class
<br>&lt;/pre&gt;
Jonathan de Halleux
Monday, June 06, 2005 4:47:26 PM UTC
Is it possible to run RowTests with rollback attribute?
Anna
Monday, June 06, 2005 4:47:26 PM UTC
Yes, row test support decorators.
Jonathan de Halleux
Monday, June 06, 2005 4:47:26 PM UTC
ShowUsYour
Monday, June 06, 2005 4:47:26 PM UTC
ShowUsYour
Monday, June 06, 2005 4:47:26 PM UTC
Anna -
<br>I'm with you, got the same error.
<br>
<br>Jonathan -
<br>sorry to say your &quot;verified and compiled sample&quot;
<br>didn't work also, keep same error.
<br>and i needed to add the
<br>Imports <a title="MbUnit, Generating Unit Testing and Model Based Testing Framework for .NET Framework" href="http://mbunit.tigris.org" target="_blank">MbUnit</a>.Core.Framework
<br>for clear other problems.
<br>
<br>but it was great your link to that article, its simple but ok. thanks
Jorge FlorĂȘncio
Comments are closed.