Monday, March 24, 2014

NUnit TestCase and TestCase source

How do you test different input values for the method under test? Let's say that you want to test a class that converts sentences into single camel-case words. As an example, "what does the fox say" should be converted to "WhatDoesTheFoxSay".

There are many scenarios that need to be tested. There can be one or more words in the sentence. We should also test for an empty string. This can lead to many almost identical tests:


[Test]
public void MakeCamelCase_CalledWithEmptyString_ReturnsEmptyString()
{
  // Arrange
  var input = string.empty;

  // Act
  var result = StringTools.MakeCamelCase(input);

  // Assert
  Assert.AreEqual(string.empty, result);
}

[Test]
public void MakeCamelCase_CalledWithOneWord_ReturnsThatWord()
{
  // Arrange
  var input = "ab";

  // Act
  var result = StringTools.MakeCamelCase(input);

  // Assert
  Assert.AreEqual("Ab", result);
}

[Test]
public void MakeCamelCase_CalledWithTwoWords_ReturnsCamelCase()
{
  // Arrange
  var input = "ab cd";

  // Act
  var result = StringTools.MakeCamelCase(input);

  // Assert
  Assert.AreEqual("AbCd", result);
}


...and the list goes on. So how can we avoid repeating those almost identical tests?

TestCase

In NUnit, the test attribute TestCase comes to the rescue! Simply use one single test and provide multiple test cases as inputs:


[TestCase(string.empty, string.empty)]
[TestCase("ab", "Ab")]
[TestCase("ab cd", "AbCd")]
[TestCase("ab cd ef", "AbCdEf")]
public void MakeCamelCase_CalledWithString_ReturnsCamelCase(string input, string expectedResult)
{
  // Act
  var result = StringTools.MakeCamelCase(input)

  // Assert
  Assert.AreEqual(expectedResult, result);
}


Now these three tests plus an additional test case with three words are collapsed to one single easy-to-read test.

Note that the [TestCase] attribute can take any number of parameters, including input and expected output. The test itself takes those parameters as input.

With NUnit 2.5 or newer, the input and result parameters can be made a bit more readable by using the named attribute parameter Result:


[TestCase(string.empty, Result = string.empty)]
[TestCase("ab", Result = "Ab")]
[TestCase("ab cd", Result = "AbCd")]
[TestCase("ab cd ef", Result = "AbCdEf")]
public string MakeCamelCase_CalledWithString_ReturnsCamelCase(string input)
{
  // Act
  var result = StringTools.MakeCamelCase(input)

  return result;
}

TestCaseSource

All this is great, but [TestCase] has a limitation: it can only take constant expressions as parameters. If we were to test a mathematical algorithm on an input class like a Vector, we couldn't have used [TestCase]. There is another option, though: the TestCaseSource attribute:


[Test, TestCaseSource("VectorDotProductCases")]
public void DotProduct_CalledWithAnotherVector_ReturnsDotProduct(Vector lhs, Vector rhs, double expectedResult)
{
  // Act
  var dotProduct = lhs.DotProduct(rhs);

  // Assert
  Assert.AreEqual(expectedResult, dotProduct);
}

private static readonly object[] VectorDotProductCases =
{
  new object[] { new Vector(1,2,3), new Vector(0,0,0), 0 },
  new object[] { new Vector(1,2,3), new Vector(4,5,6), 32 },  
};

This is slightly less readable than using TestCase, but still it's better than replicating the tests for each set of input data.


No comments:

Post a Comment