Data Driven MSTest Unit Tests With Inline Data

Posted by AgileCoder on January 8, 2015

For several years now testing frameworks like JUnit, NUnit and mb-unit have included data-driven or parameterized tests that allow you to write your test once and have it loop through a set of data rows stored as test attributes. For example (from the NUnit documentation) you can use:

[TestCase(12,3,4)]
[TestCase(12,2,6)]
[TestCase(12,4,3)]
public void DivideTest(int n, int d, int q)
{
  Assert.AreEqual( q, n / d );
}

Unfortunately, the Microsoft Test Framework does not include (as of VS 2014r4 and the 4.5 framework) similar functionality for anything other than Windows Phone App testing. There are a number of workarounds. The one pushed by MS is to use Data-Driven Test, connecting your test to an external data source like an XML or CSV file or an actual SQLServer database. This has been frustrating and unacceptable to me for several reasons:

  • It makes my tests dependent on an external resource that I may not control, or that I have to now maintain and release, making it more of an integration test than a unit test.
  • I spent a long weekend trying to get the connections, data formatting and test all working right using XML - far more overhead than I want to deal with.
  • The test data is not visible in the location of the test, making the test less scannable for the reader and harder to understand.
Because of this and other reasons, for most of my serious personal projects I use NUnit, but at work I am assigned to a very large (for me at least) project that has already settled on using the Microsoft suite of testing tools. (As an aside, this would be great if we were using Team Foundation Server, but we are using SVN, Hudson and Sonar which present their own set of configuration headaches...). 
 
While working with a co-worker yesterday to write a test for an object's Validate() method that could fail for several reasons we struck on an idea that I really like. We used an array of Anonymous Types to store our set of conditions, and then used LINQ's ForEach() method to loop through the array and run the test for each element.
 
To give you an idea of how this works, I have copied an extension method from one of my other projects. This method is used determine the proper Quarter for a given Date, and allows you to offset that quarter.
public static class Extensions
{
	/// <summary>
	/// Get the Qtr number where this date falls under.
	/// </summary>
	/// <param name="parmDate">Date to get quarter for</param>
	/// <param name="offset">offset to add/subtract from quarter</param>
	/// <returns>Qtr Number</returns>
	public static int GetQuarterNumber(this DateTime parmDate, int offset = 0)
	{
		return (int)Math.Ceiling(parmDate.AddMonths(offset * 3).Month / 3m);
	}
}
Prior to our breakthrough, the existing test code what method after method testing all of the options...
[TestMethod]
public void MonthJanuaryReturnsQuarterOne()
{
	//Arrange
	DateTime dt = new DateTime(2013, 1, 1);
	//Act
	int qtr = dt.GetQuarterNumber();
	//Assert
	Assert.AreEqual(1, qtr);
}
// And 12 more just like this...

[TestMethod]
public void MonthFebruaryOffsetTwoReturnsQuarterThree()
{
	//Arrange
	DateTime dt = new DateTime(2013, 2, 1);
	//Act
	int qtr = dt.GetQuarterNumber(2);
	//Assert
	Assert.AreEqual(3, qtr);
}
// And a couple hundred lines testing different months and offsets both (+) and (-)
Here are the two test methods that replace that several hundred lines of code using Anonymous Types and LINQ with an Anonymous function.
[TestMethod]
public void MonthReturnsProperQuarter()
{
	// Arrange
	var values = new[] {
		new { inputDate = new DateTime(2013, 1, 1), expectedQuarter = 1},
		new { inputDate = new DateTime(2013, 2, 1), expectedQuarter = 1},
		new { inputDate = new DateTime(2013, 3, 1), expectedQuarter = 1},
		new { inputDate = new DateTime(2013, 4, 1), expectedQuarter = 2},
		new { inputDate = new DateTime(2013, 5, 1), expectedQuarter = 2},
		new { inputDate = new DateTime(2013, 6, 1), expectedQuarter = 2},
		new { inputDate = new DateTime(2013, 7, 1), expectedQuarter = 3},
		new { inputDate = new DateTime(2013, 8, 1), expectedQuarter = 3},
		new { inputDate = new DateTime(2013, 9, 1), expectedQuarter = 3},
		new { inputDate = new DateTime(2013, 10, 1), expectedQuarter = 4},
		new { inputDate = new DateTime(2013, 11, 1), expectedQuarter = 5},
		new { inputDate = new DateTime(2013, 12, 1), expectedQuarter = 4}
	};
	values.ToList().ForEach(val =>
		{
			// Act
			int actualQuarter = val.inputDate.GetQuarterNumber();
			// Assert
			Assert.AreEqual(val.expectedQuarter, actualQuarter,
				"Failed for inputDate={0} and expectedQuarter={1}.", val.inputDate, val.expectedQuarter);
		});
}

[TestMethod]
public void MonthReturnsProperQuarterWithOffset()
{
	// Arrange
	var values = new[] {
		new { inputDate = new DateTime(2013, 1, 1), offset = 1, expectedQuarter = 2},
		new { inputDate = new DateTime(2013, 1, 1), offset = -1, expectedQuarter = 4},
		new { inputDate = new DateTime(2013, 4, 1), offset = 1, expectedQuarter = 3},
		new { inputDate = new DateTime(2013, 4, 1), offset = -1, expectedQuarter = 1},
		new { inputDate = new DateTime(2013, 7, 1), offset = 1, expectedQuarter = 4},
		new { inputDate = new DateTime(2013, 7, 1), offset = -1, expectedQuarter = 2},
		new { inputDate = new DateTime(2013, 10, 1), offset = 1, expectedQuarter = 1},
		new { inputDate = new DateTime(2013, 10, 1), offset = -1, expectedQuarter = 3}
        // Could add as many rows as you want, or extract to a private method that
        // builds the array of data
	}; 
	values.ToList().ForEach(val => 
	{ 
		// Act 
		int actualQuarter = val.inputDate.GetQuarterNumber(val.offset); 
		// Assert 
		Assert.AreEqual(val.expectedQuarter, actualQuarter, 
			"Failed for inputDate={0}, offset={1} and expectedQuarter={2}.", val.inputDate, val.offset, val.expectedQuarter); 
		}); 
	}
}
Two things to note:
  • Like any other test with multiple Asserts, the first failed Assert will short circuit the rest of the test. So you can have multiple failures in your rows of data and if you are using a Red-Green-Refactor pattern without carefully scanning the data rows you will repeatedly fail and fix the test.
  • Both tests include a formatted message for the Assert.AreEqual() method. This is important, because if the test fails, it will tell you by default what the expected and actual values were, but that is often not enough information to quickly determine which row in the values array contained the data that failed.


cialis generika kamagra australia cialis preise cialis kaufen cialis bestellen levitra generika viagra online kaufen kamagra bestellen cialis generika kamagra oral jelly kamagra 100mg prix cialis cialis generique kamagra gel viagra prix viagra pour homme acheter cialis kamagra prix viagra pas cher cialis 20mg levitra 20
Levitra Generika kaufen Kaufen Levitra Generika Online Cialis ohne Rezept kaufen Viagra online Viagra Generika kaufen Cialis Soft Tabs kaufen Sildenafil kaufen Apcalis Oral Jelly kaufen Viagra Soft Tabs bestellen Cialis Original kaufen Viagra bestellen Viagra bestellen Propecia Generika kaufen Viagra und seine Analoga Original Testpakete Levitra und Kamagra Erfahrungen Viagra Soft Tabs Kamagra kaufen
kamagra kopen in de winkel viagra werking cialis kopen in nederland cialis bijwerkingen kamagra bijsluiter levitra bijwerkingen viagra kopen apotheek cialis erfaring cialis i norge viagra effekt hva er kamagra viagra nettbutikk levitra eller cialis kamagra gel comprar cialis efeitos secundarios viagra farmacia cialis bula viagra infarmed levitra comprimidos cialis vs viagra cialis flashback kamagra tjejer kamagra effekt levitra fass kamagra oral jelly opiniones levitra generico precio cialis venta kamagra sobres cialis efectos secundarios
Levitra Soft Cialis Professional Levitra Original Cialis Daily Red Viagra Cialis Jelly Acheter Levitra Viagra Professional Viagra Gold - Vigour Kamagra Jelly Viagra pour femme viagra kaufen cialis kaufen Erektile Dysfunktion Viagra Professional Viagra Jelly cialis online kopen Levitra Soft viagra voor vrouwen Viagra Soft
Prezzo Levitra Acquisto Red Viagra Acquisto Cialis Disfunzione Erettile Dopo Prostatectomia Radicale Acquisto Propecia Cialis Generico Viagra Originale senza ricetta Comprare Red Viagra Comprare Levitra Generico On Line Comprare Levitra Online Disfunzione Erettile Blocco Psicologico Levitra Vendita Comprare Propecia Acquisto Female Viagra Acquisto Cialis Jelly Viagra Super Active Levitra Originale Viagra online Levitra Compresse Divisibili Disfunzione Erettile Ecografia