Tuesday, December 3, 2013

NUnit TestCase vs. TestCaseSource attributes

NUnit is my favorite framework for unit testing C#.NET code.  I have long been a fan of the TestCase attribute for testing multiple scenarios where the test logic is identical.  For example, TestCase is fantastic for checking edge cases.


Here is a simplistic class for calculating the difference between 2 dates.  If the dates represent the same day, the result should be a TimeSpan of 0, otherwise calculate the actual difference.

  1. public class Calculator
  2. {
  3.     public static TimeSpan SubtractDates(DateTime firstDate, DateTime secondDate)
  4.     {
  5.         return firstDate.Date == secondDate.Date
  6.             ? new TimeSpan(0)
  7.             : secondDate - firstDate;
  8.     }
  9. }

I can use the TestCase attribute to confirm that dates from previous or subsequent days return a TimeSpan with an Hours value that is not 0, but dates from the same day always return a TimeSpan with an Hours value of 0.

  1. [TestCase(1, 0, -9)]
  2. [TestCase(2, 8, 0)]
  3. [TestCase(2, 9, 0)]
  4. [TestCase(2, 10, 0)]
  5. [TestCase(3, 0, 15)]
  6. public static void Test_Calculator_SubtractDates_Using_TestCase(int day, int hour, int expectedHours)
  7. {
  8.     var firstDate = new DateTime(2000, 6, 2, 9, 0, 0);
  9.     var secondDate = new DateTime(2000, 6, day, hour, 0, 0);
  10.  
  11.     TimeSpan actual = Calculator.SubtractDates(firstDate, secondDate);
  12.     Assert.That(actual.Hours, Is.EqualTo(expectedHours));
  13. }

TestCase is limited in that the constructor will accept only constant, typeof, or array creation expressions.  This can be a pain if you want to test various scenarios with a range of objects.  For example, I am not able to do this with TestCase:

  1. [TestCase(new DateTime(2000, 6, 1, 0, 0, 0), -9)]
  2. [TestCase(new DateTime(2000, 6, 2, 8, 0, 0), 0)]
  3. [TestCase(new DateTime(2000, 6, 2, 9, 0, 0), 0)]
  4. [TestCase(new DateTime(2000, 6, 2, 10, 0, 0), 0)]
  5. [TestCase(new DateTime(2000, 6, 3, 0, 0, 0), 15)]
  6. public static void Test_Calculator_SubtractDates_Using_TestCase(DateTime secondDate, int expectedHours)
  7. {
  8.     var firstDate = new DateTime(2000, 6, 2, 9, 0, 0);
  9.  
  10.     TimeSpan actual = Calculator.SubtractDates(firstDate, secondDate);
  11.     Assert.That(actual.Hours, Is.EqualTo(expectedHours));
  12. }

If I attempt to run these tests, I get an error “An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type.”


I just recently discovered the TestCaseSource attribute which can be used to work around the limitations of TestCase while providing the same functionality.  Here is the same set of unit tests using TestCaseSource:

  1. [Test, TestCaseSource("SecondDateCases")]
  2. public static void Test_Calculator_SubtractDates_Using_TestCaseSource(DateTime secondDate, int expectedHours)
  3. {
  4.     var temp = SecondDateCases.ToString();
  5.     var firstDate = new DateTime(2000, 6, 2, 9, 0, 0);
  6.  
  7.     TimeSpan actual = Calculator.SubtractDates(firstDate, secondDate);
  8.     Assert.That(actual.Hours, Is.EqualTo(expectedHours));
  9. }
  10. private static object[] SecondDateCases =
  11. {
  12.     new object[] { new DateTime(2000, 6, 1, 0, 0, 0), -9 },
  13.     new object[] { new DateTime(2000, 6, 2, 8, 0, 0), 0 },
  14.     new object[] { new DateTime(2000, 6, 2, 9, 0, 0), 0 },
  15.     new object[] { new DateTime(2000, 6, 2, 10, 0, 0), 0 },
  16.     new object[] { new DateTime(2000, 6, 3, 0, 0, 0), 15 }
  17. };

For more information, consult the NUnit 2.6.3 Documentation for TestCase and TestCaseSource.

No comments:

Post a Comment