This is an extract from the Unit Testing Succinctly eBook, by Marc Clifton, kindly provided by Syncfusion.
In this article, we're going to begin talking about a few of the advanced topics that come with unit testing. This includes things such as cyclometric complexity, methods, fields, and reflection.
Cyclometric Complexity
Cyclometric complexity is a measure of the number of independent paths through your code. Code coverage testing is intended to ensure that your tests execute all possible code paths. Code coverage testing is available in Visual Studio’s Test, Premium, and Ultimate versions. Code coverage is not part of NUnit. A third-party solution, NCover, is also available.
To illustrate this issue, consider this code, which parses command line parameters:
public static class CommandLineParser { /// /// Returns a list of options based on brute force parsing /// of command line options. The function returns the /// specified options and the option parameter if required. /// public static Dictionary<string, string> Parse(string cmdLine) { Dictionary<string, string> options = new Dictionary<string, string>(); string[] items = cmdLine.Split(' '); int n = 0; while (n < items.Length) { string option = items[n]; string param = String.Empty; if (option[0] != '-') { throw new ApplicationException("Expected an option."); } if (option == "-f") { // Has the parameter been supplied? if (items.Length <= n + 1) { throw new ApplicationException("Filename not specified."); } param = items[n + 1]; // Is it a parameter or another option? if (param[0] == '-') { throw new ApplicationException("Filename not specified."); } ++n; // Skip the filename option parameter. } options[option] = param; ++n; } return options; } }
and a couple of simple tests (note that these tests omit the code paths that result in exceptions being thrown):
[TestFixture] public class CodeCoverageTests { [Test] public void CommandParserTest() { Dictionary<string, string> options = CommandLineParser.Parse("-a -b"); Assert.That(options.Count == 2, "Count expected to be 2"); Assert.That(options.ContainsKey("-a"), "Expected option '-a'"); Assert.That(options.ContainsKey("-b"), "Expected option '-b'"); } [Test] public void FilenameParsingTest() { Dictionary<string, string> options = CommandLineParser.Parse("-f foobar"); Assert.That(options.Count == 1, "Count expected to be 1"); Assert.That(options.ContainsKey("-f"), "Expected option '-f'"); Assert.That(options["-f"] == "foobar"); } }
Now let’s look at what a code coverage test might look like, first by writing a poor man’s code coverage helper:
public static class Coverage { public static List CoveragePoints {get; set;} public static void Reset() { CoveragePoints = new List(); } [Conditional("DEBUG")] public static void Set(int coveragePoint) { CoveragePoints.Add(coveragePoint); } }
We’ll also need a simple extension method; you’ll see why in a minute:
public static class ListExtensions { public static bool HasOrderedItems(this List itemList, int[] items) { int n = 0; foreach (int i in itemList) { if (i != items[n]) { return false; } ++n; } return true; } }
Now we can add coverage set-points in our code, which will be compiled into the application when it is compiled in DEBUG mode (the bold red lines where added):
public static class CommandLineParser { /// /// Returns a list of options based on brute force parsing /// of command line options. The function returns the /// specified options and the option parameter if required. /// public static Dictionary<string, string> Parse(string cmdLine) { Dictionary<string, string> options = new Dictionary<string, string>(); string[] items = cmdLine.Split(' '); int n = 0; while (n < items.Length) { Coverage.Set(1); // WE ADD THIS COVERAGE SET-POINT string option = items[n]; string param = String.Empty; if (option[0] != '-') { throw new ApplicationException("Expected an option."); } if (option == "-f") { Coverage.Set(2); // WE ADD THIS COVERAGE SET-POINT // Has the parameter been supplied? if (items.Length <= n + 1) { throw new ApplicationException("Filename not specified."); } param = items[n + 1]; // Is it a parameter or another option? if (param[0] == '-') { throw new ApplicationException("Filename not specified."); } ++n; // Skip the filename option parameter. } options[option] = param; ++n; } return options; } }
And now we can write the following test fixture:
[TestFixture] public class CommandParserCoverageTests { [SetUp] public void CoverageSetup() { Coverage.Reset(); } [Test] public void CommandParserTest() { Dictionary<string, string> options = CommandLineParser.Parse("-a -b"); Assert.That(Coverage.CoveragePoints.HasOrderedItems(new [] {1, 1})); } [Test] public void FilenameParsingTest() { Dictionary<string, string> options = CommandLineParser.Parse("-f foobar"); Assert.That(Coverage.CoveragePoints.HasOrderedItems(new [] { 1, 2 })); } }
Note how we are now ignoring the actual results, but are ensuring that the desired code blocks are being executed.
White Box Testing: Inspecting Protected and Private Fields and Methods
Arguably, a unit test should only concern itself with public fields and methods. The counterargument for this is that in order to test the entire implementation, access to protected or private fields, to assert their state, and the ability to unit test protected or private methods is required. Considering that it is probably not desirable to expose most low-level computations, and those are exactly the methods one wants to test, it is most likely that testing at least protected or private methods is necessary. There are several options available.
Exposing Methods and Fields in Test Mode
This example illustrates the concept:
public class DoesSomething { #if TEST public #else private #endif void SomeComputation() { } }
While this is doable, it produces ugly source code and runs the serious risk that someone might actually call the method with the symbol TEST defined, only to discover that his or her code breaks in a production build where the TEST symbol is undefined.
Deriving a Test Class
As an alternative, if the methods are protected, consider deriving a test class:
public class DoesSomethingElse { protected void SomeComputation() { } } public class DoesSomethingElseTesting : DoesSomethingElse { public void TestSomeComputation() { base.SomeComputation(); } }
This allows you to instantiate the derived testing class and access a protected method via a publicly exposed method in the subclass.
Reflection
Lastly, one can use reflection for private methods or sealed classes. The following illustrates a private method and executing that method via reflection in a test:
public class DoesSomething { private void SomeComputation() { } } [TestClass] public class DoesSomethingTest { [TestMethod] public void SomeComputationTest() { DoesSomething ds = new DoesSomething(); Type t = ds.GetType(); MethodInfo mi = t.GetMethod("SomeComputation", BindingFlags.Instance | BindingFlags.NonPublic); mi.Invoke(ds, null); } }
Comments