Unit testing your external calls

Andrew Kazyrevich
 
In a recent blog post boldly named How to debug scripts in Report SharpShooter your humble correspondent (that is, me) claimed it’s not possible to debug scripts per se but revealed a striking alternative: turning your scripts into C# methods and calling those methods from your report.
 
I dubbed this approach “external calls” (which may or may not be correct) and rounded up listing the benefits of it:

  • make debugging possible which in turn makes coding a bit less painful
  • allow code sharing as any report may call methods of a central WinForms app
  • increase testability because you can write automated unit tests to check the code (and no longer force people testing your reports if they should be just using them ;) )

The last one about unit testing is somewhat important (and, alas, too often neglected) so today we will take the C# code from that post I mentioned, and refactor and unit test it.
 
(Feel free to browse the source code for this post on BitBucket, or download a zip archive!)

Step 1 of 3. What to test.

 
Unit testing means testing in isolation: each test verifies a single function, taken out of context, usually by passing over some predefined values and checking the results.
 
In our case, we should test the standard deviation algorithm. Here’s the original code:

  public partial class Form1 : Form
  {
      private static readonly Dictionary _hedgeFunds = ...

      public static bool IsWithinStandardDeviation(object value)
      {
          int total = _hedgeFunds.Values.Count;
          double mean = _hedgeFunds.Values.Sum() / total;
          double variance = 
                  _hedgeFunds.Sum(x => Math.Pow(x.Value - mean, 2)) / total;
          double deviation = Math.Sqrt(variance);
 
          bool result = (Math.Abs((double) value - mean) <= deviation);
          return result;
      } 
      ...
  }

As you can see, we form the interval from the values of _hedgeFunds dictionary, and then an arbitrary value can be tested against the standard deviation of that interval.
 
Cool beans, so our test should be the following: come up with a predefined interval with a known standard deviation, pass the predefined values into the algorithm, and check if the results are correct.
 
But, as astute readers already noticed, IsWithinStandardDeviation cannot be tested in its current form, because the initial interval is hardcoded in _hedgeFunds dictionary and we cannot substitute it with our "test values".
 
Therefore, we need to re-shape the original code, without changing its logic - a process often called "refactoring".

Step 2 of 3. Refactoring.

 
So we should introduce a new class which will take the interval in its constructor: in this case we can reuse that class in both Form1 and the test:

  internal class Interval
  {
     internal Interval(IEnumerable<double> values)
     {
        ...
     }

     internal bool HasWithinDeviation(double value)
     {
        ...
     }
  }

Notice that while making the code testable we've accidentally made it more readable as well ;) because here's how Form1 looks like now:

  public partial class Form1 : Form
  {
      private static readonly Dictionary _hedgeFunds = ...

      public Form1()
      {
            ...
            _interval = new Interval(_hedgeFunds.Values);
      }

      public static bool IsWithinStandardDeviation(object value)
      {
          return _interval.HasWithinDeviation((double)value);
      } 

      ...
  }

Step 3 of 3. Unit tests.

 
Refactoring has made it possible to write unit tests - and here we go, testing HasWithinDeviation of the newly introduced Interval class.
 
As the poster child let's take a data sample from wikipedia article on the standard deviation. Their setup:

  • list is 2,4,4,4,5,5,7,9
  • which gives the mean of 5 and the deviation of 2

In other words, all numbers within 5±2 should yield true in our algorithm - so let's test that. First off, we create Tests.ExternalMethodCalls project next to the existing ExternalMethodCalls one (I've heard debates on whether "Test" should be a prefix or a suffix in the name of a testing project, and I've seen both ways over my career, and now prefer the prefix approach).
 
Then we need to decide which values to use in the tests. A rule of thumb is to analyze all possible inputs (it's usually infinite number of them), put them into logical groups, and then test a few items from each group. In our case, we know that only values within 5±2 should satisfy the algorithm. So we need to choose some of them - and explicitly test the edges of the interval! - and also choose some values which should bring up false.
 
This is how the tests may look like (using NUnit as our testing framework):

  [TestFixture]
  public class IntervalTests
  {
     private readonly Interval _interval = new Interval(new double[] { 2, 4, 4, 4, 5, 5, 7, 9 });

     [Test]
     public void ValuesAcceptedWithin()
     {
          Assert.That(_interval.HasWithinDeviation(4), Is.True);
          Assert.That(_interval.HasWithinDeviation(5), Is.True);
          Assert.That(_interval.HasWithinDeviation(6), Is.True);
     }

     [Test]
     public void ValuesAcceptedOnEdges()
     {
          Assert.That(_interval.HasWithinDeviation(3), Is.True);
          Assert.That(_interval.HasWithinDeviation(7), Is.True);
     }

     [Test]
     public void ValuesRejectedOutside()
     {
          Assert.That(_interval.HasWithinDeviation(2), Is.False);
          Assert.That(_interval.HasWithinDeviation(8), Is.False);
     }
  }

 
We might have stopped on that, but I'd like to show you another trick - parametrized tests. Notice that every single tests above has repeated code which differs only by the value in the Assert. What we can do is to make it a parameter in the tests, like this:

     [Test]
     [TestCase(4)] [TestCase(5)] [TestCase(6)]
     public void ValuesAcceptedWithin(double value)
     {
          Assert.That(_interval.HasWithinDeviation(value), Is.True);
     }

And once the above is done, we can run NUnit GUI and enjoy the green light of happy tests:
 

 

Summary.

Unit testing is a good way of making sure the code works as expected (both the new code you've just written, and the old code you may have changed on the way). Also, it often serves well as "live" documentation for developers, especially when the real documentation is incomplete or, dare I say, missing :) .
 
You can incorporate such automated tests into the build script, and each time the application gets built you can be sure that every single test has passed successfully.
 
Source code discussed in this post is available on BitBucket. In general, unit testing in Report SharpShooter may be a good idea if you have complex logic in your reports, or if that logic frequently changes. On the other hand, it might be a waste of time if your scripts or external calls are trivial one-liners. It all depends, and it's up to you to decide!
 

August 7th, 2011

Leave a Comment