Does this sound familiar? You just finished your company sponsored training on unit testing or reading that blog bestowing the wonders of unit testing and are all excited to start adding unit tests to your current project…then go sh*t where do I even start?
This is exactly what happened to me the first time I was exposed to unit testing. The company I work for brought in experts to teach us about Test Driven Development and Unit Testing. The training made it see so simple you just test the function units of your code…its super simple…right. The examples that were used in the training made it seem even easier as the tests and functional units were trivial… does this example look familiar?
public class Calculator
{
public int Add(int first, int second)
{
return first + second;
}
}
[TestMethod]
public void WhenTwoIntegersPassedIn_AddthemTogetherReturnResult()
{
//Arrange
int a = 5;
int b = 10;
Calculator calc = new Calculator();
//Act
int answer = calc.Add(5, 10);
//Assert
Assert.AreEqual(15, answer);
}
I personally don’t see why anyone would ever make a unit test like this. Is the world of mathematics going to change and positive integer 5 plus positive integer 10 is no longer going to equal 15? I doubt it.
I currently just finished a project where unit test coverage was very important as the app will be taking in strings of variable size and formatting to get processed. Knowing how the app will behave given different situations was critical. These input strings were formatted by clients so what that really means is some will be in the correct format and some will be well…anything.
I’m going to focus on just one aspect of the app, the parsing of fixed width strings. Hopefully, after reading though the examples below you have a better idea of how to create methods and classes that are easier to unit test.
First we need to tell the app how to parse the string and we need a some way to store that. I went with Xml to store the metadata.
<parserspec>
<field name="Test1" start="0" end="5"/>
<field name="Test2" start="6" end="10"/>
<field name="Test3" start="11" end="20"/>
<field name="Test4" start="21" end="25"/>
</parserspec>
The <parserspec> xml can be deserialized into an object that can then be used to parse the fixed width string. Code to do that is below, this is a slimed down version of the code, error handling was removed for brevity.
[XmlRoot(ElementName = "parserspec")]
public class ParserSpec
{
[XmlElement(ElementName = "field")]
public List<FixedWidthField> Fields { get; set; }
public static ParserSpec GetParserSpec(string xmlMap)
{
try
{
ParserSpec spec = (ParserSpec)Activator.CreateInstance(typeof(ParserSpec));
XmlSerializer xmlSerializer = new XmlSerializer(typeof(ParserSpec));
XmlReaderSettings readerSettings = new XmlReaderSettings();
readerSettings.DtdProcessing = DtdProcessing.Ignore;
using (StringReader sr = new StringReader(xmlMap))
using (XmlReader xmlReader = XmlReader.Create(sr, readerSettings))
{
spec = (ParserSpec)xmlSerializer.Deserialize(xmlReader);
}
return spec;
}catch(Exception ex)
{
Console.WriteLine(ex.ToString());
return null;
}
}
}
public class FixedWidthField
{
[XmlAttribute(AttributeName ="name")]
public string Name { get; set; }
[XmlAttribute(AttributeName ="start")]
public int Start { get; set; }
[XmlAttribute(AttributeName = "end")]
public int End { get; set; }
[XmlText]
public string Value { get; set; }
}
The code above is kind of outside the scope for this post but its used in the unit tests coming up so I included it so you can get the full understanding of what the code is doing.
Okay so lets get on to the class that is actually going to do the work. Below we have the FixedWidthParser class that Inherits from ParserBase. In order to make this class testable we need some public methods and in my experience methods that accept data and return data make unit testing easier.
When writing or designing a new class that I know is going to require unit testing I ask myself the following questions?
- What data does this class need to do its job?
- How is the class going to get its setup data?
- passed in when instantiated?
- passed in via public getters setters?
- hybrid of both 1 and 2?
- What input data will the class be performing work on?
- What output data will the class need to return?
public abstract class ParserBase
{
protected ParserSpec pSpec;
public ParserBase(ParserSpec parserSpec)
{
//should check for null but this is just an example
pSpec = parserSpec;
}
public abstract Dictionary<string, string> GetNameValuePairs(string inputString);
}
public class FixedWidthParser : ParserBase
{
public FixedWidthParser(ParserSpec parserSpec)
: base(parserSpec) { }
public override Dictionary<string, string> GetNameValuePairs(string inputString)
{
Dictionary<string, string> nameValuePairs = GetNameValuePairsWorker(inputString);
return nameValuePairs;
}
private Dictionary<string,string> GetNameValuePairsWorker(string inputString)
{
Dictionary<string,string> nameValuePairs = new Dictionary<string, string>();
foreach (FixedWidthField field in pSpec.Fields)
{
if (field.End <= inputString.Length)
{
string substring = inputString.Substring(field.Start, field.End - field.Start);
if(nameValuePairs.ContainsKey(field.Name))
{
nameValuePairs[field.Name] = substring;
}
else
{
nameValuePairs.Add(field.Name, substring);
}
}
}
return nameValuePairs;
}
}
The FixedWidthParser class requires the ParserSpec object in order to complete its work so we do have a dependency and that is fine because this is the real world and classes have dependencies. FixedWidthParser only has one public method and that is what we are going to be writing unit tests for.
Lets examine the GetNameValuePairs function. First it takes an string input that’s good as we can pass in different data easily without having to re-instantiate the class. It also returns a Dictionary<> a object that makes it fast easy to check for certain values and count how key value pairs are present. Lets also break down what the method is doing.
For each field that is in the ParserSpec object it checks to make sure we are not going to exceed the bounds of our string, if we are within the bounds then we take the substring and attempt to add it to the dictionary or update the value if the key is already present.
So lets create our first unit test, checking to see if we get the correct number of entries based on the parserspec that we are using.
First setup the data
[TestClass]
public class UnitTest1
{
public string fixedSample = "0123456789101112131420219";
public string parserspec = @"<parserspec>
<field name = ""Test1"" start = ""0"" end = ""5"" />
<field name = ""Test2"" start = ""5"" end = ""10"" />
<field name = ""Test3"" start = ""10"" end = ""20"" />
<field name = ""Test4"" start = ""20"" end = ""25"" />
</parserspec>";
public ParserSpec pSpec;
Here we have a fixed width string with known values and a known parserspec xml. The ParserSpec object will be assigned in the unit test.
Now for our first test
[TestMethod]
public void WhenGivenParserSpecWithFourFields_ExpectFourPairsFromProperInput()
{
//Arrange
pSpec = ParserSpec.GetParserSpec(parserspec);
FixedWidthParser fixedParser = new FixedWidthParser(pSpec);
//Act
Dictionary<string, string> nameValuePairs = fixedParser.GetNameValuePairs(fixedSample);
//Assert
Assert.AreEqual(4, nameValuePairs.Count);
}
I like to use the Arrange-Act-Assert pattern when setting up test. So first we setup the data we will be using in the test and create the needed object.
Then we run the functional unit of code we are testing.
Finally we check to make sure that the code behaves as expected.
This is just one simple example of a unit test, there are several other cases that should be considered as well. What if the string is empty or null how does the code behave? What happens if there are more fields in the parserspec xml than the string contains? What if parserspec has no fields?
As you can see setting up unit tests in the real world takes a bit more time than just the simple test that was at the beginning of the post. However, when you are designing your classes if you keep testing in mind and create functions that are easy to test like the GetNameValuePairs one above you can create quite a bit of tests relatively easy. Having good test coverage always gives me peace of mind when a new app is going to production.