Test multiple test cases with one test using TestCase and NUnit 2.5

February 17 2012

TestCase

The [TestCase] attribute in NUnit 2.5 and above allows the execution of one test multiple times with different parameters.  As a real life example, I have a PointsSystem class in my F1 stats website which is responsible for determinig the points to award a particular race entry result.  I have a RaceEntry object which contains qualifying times, race times, and race finish positions.  From this I want to determine the number of points finishing the race in a particular position will yield.

These test are a little contrived because I'm testing data, not execution.  Different points systems award different points for different position.  Some award points down to 10th place, or 20th, while some only reward to 6th.  The value of points awarded is so important to the stats site I decided that I'd go ahead and ensure my calculator would return the desired results.

To do this I originally had a test case for every position that award points

[TestFixture]
public class PointsSystem2010Fixture 
{
    private IPointsSystem _pointsSystem;

    [SetUp]
    public void SetUp()
    {            
        _pointsSystem = new PointsSystem2010();
        CacheHelper.InvalidateAll();
    }

    [Test]
    public void _01st_place_race_and_qualifying_gets_30_points()
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, 1);            

        // Act
        var points = _pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(30, points);
    }

    [Test]
    public void _02nd_place_gets_22_points()
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, 2);

        // Act
        var points = _pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(22, points);
    }

    [Test]
    public void _03rd_place_gets_18_points()
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, 3);

        // Act
        var points = _pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(18, points);
    }

    [Test]
    public void _04th_place_gets_14_points()
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, 4);

        // Act
        var points = _pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(14, points);
    }

    [Test]
    public void _05th_place_gets_11_points()
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, 5);

        // Act
        var points = _pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(11, points);
    }

    [Test]
    public void _06th_place_gets_8_points()
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, 6);

        // Act
        var points = _pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(8, points);
    }

    [Test]
    public void _07th_place_gets_6_points()
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, 7);

        // Act
        var points = _pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(6, points);
    }

    [Test]
    public void _08th_place_gets_4_points()
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, 8);

        // Act
        var points = _pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(4, points);
    }

    [Test]
    public void _09th_place_gets_2_points()
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, 9);

        // Act
        var points = _pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(2, points);
    }

    [Test]
    public void _10th_place_gets_1_points()
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, 10);

        // Act
        var points = _pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(1, points);
    }

    [Test]
    public void Eleventh_place_gets_0_points()
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, 11);

        // Act
        var points = entry.Points;

        // Assert
        Assert.AreEqual(0, points);
    }
}

 

As you can see that is a lot of redundant code.  Using the [TestCase] attribute I can condense this down to just one test, with a TestCase for each value I want to test.  To do this I create parameters on the test method and pass in the place and it’s expected points result.

[TestFixture]
public class PointsSystem2010Fixture 
{
    private IPointsSystem _pointsSystem;

    [SetUp]
    public void SetUp()
    {            
        _pointsSystem = new PointsSystem2010();
        CacheHelper.InvalidateAll();
    }

    [TestCase(1, 30)]
    [TestCase(2, 22)]
    [TestCase(3, 18)]
    [TestCase(4, 14)]
    [TestCase(5, 11)]
    [TestCase(6, 8)]
    [TestCase(7, 6)]
    [TestCase(8, 4)]
    [TestCase(9, 2)]
    [TestCase(10, 1)]
    [TestCase(11, 0)]        
    public void Points_are_correct_for_race_place(int place, decimal expectedPoints)
    {
        // Arrange
        var entry = FakesFactory.RaceEntry(_pointsSystem, place);

        // Act
        var points = _pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(expectedPoints, points);
    }
}

Here are the executed tests in NUnits test runner.  I can now remove my duplicated test code Smile

image

I don't think testing the points for each place is really the same as testing data because each PointsSystem instance should and MUST always return a certain points value for a certain position, and if it doesn't, it's broken.

TestCaseSource

To complement the TestCase attribute, there's the TestCaseSource attribute which allows you to define a source for your inputs and removes the need to use a TestCaseAttribute for every test case.  TestCaseSource takes a string representing the property/method name of the property or method returning an object array containing the test case data to pass into the test.

To continue my example, I have a number of PointsSystem's that need testing. To do this I added an extra parameter to my test method that accepts the type of the PointsSystem under test. I include this type in my test case source data.  I end up with the following test case data definition:

static object[] Points2010Cases = 
    {
        new object[] { typeof(PointsSystem2010), 1, 30M }, 
        new object[] { typeof(PointsSystem2010), 2, 22M }, 
        new object[] { typeof(PointsSystem2010), 3, 18M }, 
        new object[] { typeof(PointsSystem2010), 4, 14M }, 
        new object[] { typeof(PointsSystem2010), 5, 11M }, 
        new object[] { typeof(PointsSystem2010), 6, 8M }, 
        new object[] { typeof(PointsSystem2010), 7, 6M }, 
        new object[] { typeof(PointsSystem2010), 8, 4M }, 
        new object[] { typeof(PointsSystem2010), 9, 2M }, 
        new object[] { typeof(PointsSystem2010), 10, 1M }, 
        new object[] { typeof(PointsSystem2010), 11, 0M }, 
    };

    static object[] Points2011Cases = 
    {
        new object[] { typeof(PointsSystem2011), 1, 25M }, 
        new object[] { typeof(PointsSystem2011), 2, 18M }, 
        new object[] { typeof(PointsSystem2011), 3, 15M }, 
        new object[] { typeof(PointsSystem2011), 4, 12M }, 
        new object[] { typeof(PointsSystem2011), 5, 10M }, 
        new object[] { typeof(PointsSystem2011), 6, 8M }, 
        new object[] { typeof(PointsSystem2011), 7, 6M }, 
        new object[] { typeof(PointsSystem2011), 8, 4M }, 
        new object[] { typeof(PointsSystem2011), 9, 2M }, 
        new object[] { typeof(PointsSystem2011), 10, 1M }, 
        new object[] { typeof(PointsSystem2011), 11, 0M }, 
    };

    static object[] Points1991Cases = 
    {
        new object[] { typeof(PointsSystem1991), 1, 10M }, 
        new object[] { typeof(PointsSystem1991), 2, 6M }, 
        new object[] { typeof(PointsSystem1991), 3, 4M }, 
        new object[] { typeof(PointsSystem1991), 4, 3M }, 
        new object[] { typeof(PointsSystem1991), 5, 2M }, 
        new object[] { typeof(PointsSystem1991), 6, 1M }, 
        new object[] { typeof(PointsSystem1991), 7, 0M }, 
        new object[] { typeof(PointsSystem1991), 8, 0M }, 
        new object[] { typeof(PointsSystem1991), 9, 0M }, 
        new object[] { typeof(PointsSystem1991), 10, 0M }, 
        new object[] { typeof(PointsSystem1991), 11, 0M }, 
    };

    static object[] Points1990Cases = 
    {
        new object[] { typeof(PointsSystem1990), 1, 10M }, 
        new object[] { typeof(PointsSystem1990), 2, 6M }, 
        new object[] { typeof(PointsSystem1990), 3, 4M }, 
        new object[] { typeof(PointsSystem1990), 4, 3M }, 
        new object[] { typeof(PointsSystem1990), 5, 2M }, 
        new object[] { typeof(PointsSystem1990), 6, 1M }, 
        new object[] { typeof(PointsSystem1990), 7, 0M }, 
        new object[] { typeof(PointsSystem1990), 8, 0M }, 
        new object[] { typeof(PointsSystem1990), 9, 0M }, 
        new object[] { typeof(PointsSystem1990), 10, 0M }, 
        new object[] { typeof(PointsSystem1990), 11, 0M }, 
    };

Now I can define these sources on my modified test method:

    [Test]
    [TestCaseSource("Points2010Cases")]
    [TestCaseSource("Points2011Cases")]
    [TestCaseSource("Points1991Cases")]
    [TestCaseSource("Points1990Cases")]
    public void Points_are_correct_for_race_place(Type t, int place, decimal expectedPoints)
    {
        PointsSystem pointsSystem = (PointsSystem)Activator.CreateInstance(t);

        // Arrange
        var entry = FakesFactory.RaceEntry(pointsSystem, place);

        // Act
        var points = pointsSystem.CalculatePoints(entry);

        // Assert
        Assert.AreEqual(expectedPoints, points);
    }

Post a comment

comments powered by Disqus