Saturday, November 29, 2008 1:06 AM Sean Feldman

Test Helpers and Fluent Interfaces

Today was a great day. One of the things we do with the team is experiment how we write our test. Experimenting seems the most effective way of figuring out what should be our testing approach. At this point we are mostly doing specification driven tests (unit tests).

We had a few questions in regards to code duplication and keeping it DRY and self-explanatory at the same time, hiding no important details. At the same time overwhelming details should not be annoying the test reader/explorer. 

One of the things that is crucial, is to keep the spot light on the most important in the test – subject under test (SUT) and what are the assertions about it behavior/state. Anything else is secondary, but not unimportant. Therefore, anything that is not primarily should be communicated in the simplest manner and preferably in human language with simplistic logic in order not to steal the scene from SUT and result.

The component my pair partner and I were working on was a strategy component, based on time. A very simple one with the following rules:

  1. A report can be issued to a client if reporting time is within 10 minutes in the past from the current system’s time
  2. Anything that outside of the time frame defined in #1 means no report issued to the client

Simple one.

First step was to abstract the System.DateTime object in order to manipulate the current time. There are plenty of resources on that, and despite the fact that I loved a particular testing implementation proposed by Oren here, we ended up doing contract based implementation and usage of an instance based custom SystemDateTime solution.

   1: public interface ISystemDateTime
   2: {
   3:     DateTime Now { get; }
   4: }
   1: public class SystemDateTime : ISystemDateTime 
   2: {
   3:     public DateTime Now
   4:     {
   5:         get { return DateTime.Now; }
   6:     }
   7: }

Once that  was in place, we started to craft the tests for the strategy object. But something was wrong.

   1: [Concern(typeof(TimeBasedReportExecutionStrategy))]
   2:   [TestFixture]
   3:   public class When_time_based_report_execution_strategy_is_asked_to_determine_can_report_be_executed_for_a_given_client_information : SpecificationContext<IReportExecutionStrategy>
   4:   {
   5:       private ISystemDateTime systemDateTime;
   6:       private DateTime clientReportExecutionTime = new DateTime(2008, 1, 1, 12, 0, 0);
   7:       
   8:       protected override IReportExecutionStrategy EstablishContext()
   9:       {
  10:           systemDateTime = Stub<ISystemDateTime>();
  11:  
  12:           return new TimeBasedReportExecutionStrategy(systemDateTime);
  13:       }
  14:  
  15:       protected override void BecauseOf(){}
  16:  
  17:       [Test]
  18:       public void Should_allow_report_execution_when_escalation_time_is_within_executable_threshold()
  19:       {
  20:           systemDateTime.Stub(t => { var readProperty = t.Now; })
  21:               .Return(clientReportExecutionTime.AddMinutes(5));
  22:  
  23:           var result = system_under_test.CanIssueReportFor(new ClientEscalationInfo("client_id", clientReportExecutionTime, "recipients"));
  24:           result.should_be_true();
  25:       }
  26: }

What bothered us is the expression that was setting up a stubbed version of the  system current time, “clientReportExecutionTime.AddMinutes(5)”. It was too much of information to process just to determine that we want to test what happens when SUT has a client report time that is less than 10 minutes old.

So we started to think, and gosh I love pair programming. I know it’s unnecessary to promote it any further, but just can’t stop getting excited about pair programming I recall “the days of silo”. And if you read these lines and think this dude went from the “days of silo” to the “days of psycho”, then yes, I am mad about it :)

 

 

 

Anyhow, this test was not the only one, we wanted to test more than 10 minutes in the past, as well as what happens when report time is in future, etc. How we can make it as clear as possible? This is where we went to the Test Helper class pattern. It was nice, but still not what we wanted, we wanted something that would feel natural. Next step was to think towards the natural syntax – fluent interface. After a few iterations, name changes and debates, this is what we got:

   1: [Concern(typeof(TimeBasedReportExecutionStrategy))]
   2:     [TestFixture]
   3:     public class When_time_based_report_execution_strategy_is_asked_to_determine_can_report_be_executed_for_a_given_client_information : SpecificationContext<IReportExecutionStrategy>
   4:     {
   5:         private ISystemDateTime systemDateTime;
   6:         private DateTime clientReportExecutionTime = new DateTime(2008, 1, 1, 12, 0, 0);
   7:         
   8:         protected override IReportExecutionStrategy EstablishContext()
   9:         {
  10:             systemDateTime = Stub<ISystemDateTime>();
  11:  
  12:             return new TimeBasedReportExecutionStrategy(systemDateTime);
  13:         }
  14:  
  15:         protected override void BecauseOf(){}
  16:  
  17:         [Test]
  18:         public void Should_allow_report_execution_when_escalation_time_is_within_executable_threshold()
  19:         {
  20:             systemDateTime.Stub(t => { var readProperty = t.Now; })
  21:                 .Return(TestHelper.When(clientReportExecutionTime).Is(5).minutes_in_the_past);
  22:  
  23:             var result = system_under_test.CanIssueReportFor(new ClientEscalationInfo("client_id", clientReportExecutionTime, "recipients"));
  24:             result.should_be_true();
  25:         }
  26:  
  27:         [Test]
  28:         public void Should_not_allow_report_execution_when_escalation_time_is_within_executable_threshold_in_the_future()
  29:         {
  30:             systemDateTime.Stub(t => { var readProperty = t.Now; })
  31:                 .Return(TestHelper.When(clientReportExecutionTime).Is(5).minutes_in_the_future);
  32:  
  33:             var result = system_under_test.CanIssueReportFor(new ClientEscalationInfo("client_id", clientReportExecutionTime, "recipients"));
  34:             result.should_be_false();
  35:         }
  36:  
  37:         [Test]
  38:         public void Should_not_allow_report_execution_when_escalation_time_is_outside_of_executable_threshold_in_the_past()
  39:         {
  40:             systemDateTime.Stub(t => { var readProperty = t.Now; })
  41:                 .Return(TestHelper.When(clientReportExecutionTime).Is(11).minutes_in_the_past);
  42:             var result = system_under_test.CanIssueReportFor(new ClientEscalationInfo("client_id", clientReportExecutionTime, "recipients"));
  43:  
  44:             result.should_be_false();
  45:         }
  46:  
  47:         [Test]
  48:         public void Should_not_allow_report_execution_when_escalation_time_is_outside_of_executable_threshold_in_the_future()
  49:         {
  50:             systemDateTime.Stub(t => { var readProperty = t.Now; })
  51:                 .Return(TestHelper.When(clientReportExecutionTime).Is(11).minutes_in_the_future);
  52:  
  53:             var result = system_under_test.CanIssueReportFor(new ClientEscalationInfo("client_id", clientReportExecutionTime, "recipients"));
  54:  
  55:             result.should_be_false();
  56:         }
  57: }

The TestHelper in this case was a private class that implemented the fluent interface.

   1: private class TestHelper
   2: {
   3:     private DateTime anchorTime;
   4:     private int minutesToUse;
   5:  
   6:     private TestHelper(DateTime anchorTime)
   7:     {
   8:         this.anchorTime = anchorTime;
   9:     }
  10:  
  11:     public static TestHelper When(DateTime anchorTime)
  12:     {
  13:         return new TestHelper(anchorTime);
  14:     }
  15:  
  16:     public TestHelper Is(int minutes)
  17:     {
  18:         minutesToUse = minutes;
  19:         return this;
  20:     }
  21:  
  22:     public DateTime minutes_in_the_future
  23:     {
  24:         get
  25:         {
  26:             return anchorTime.AddMinutes(minutesToUse);
  27:         }
  28:     }
  29:  
  30:     public DateTime minutes_in_the_past
  31:     {
  32:         get
  33:         {
  34:             return anchorTime.AddMinutes(-minutesToUse);
  35:         }
  36:     }
  37: }

An interesting observation we made for ourselves was that just by doing it this way, not only we simplified tests reading, but also have taken the concern of date/time math out of the real concern – testing the real SUT, the strategy.

Conclusions

  1. I am not a 100% confident this is the best way to implement a test, but this is something that my team likes, and we will keep exploring it and getting better in expressiveness in our tests.
  2. Tests should be ‘clean’ (according to Uncle Bob) or they are not communicating the idea, become un-maintainable down the road, and abandoned eventually.
  3. If it feels wrong, it is wrong. Do everything it takes to feel right.
Filed under: , ,

Comments

# Arjan`s World &raquo; LINKBLOG for November 29, 2008

Saturday, November 29, 2008 9:56 AM by Arjan`s World » LINKBLOG for November 29, 2008

Pingback from  Arjan`s World    &raquo; LINKBLOG for November 29, 2008

# re: Test Helpers and Fluent Interfaces

Saturday, November 29, 2008 11:32 AM by mo

Hey Sean,

one of the things we've had to do is create time sensitive specifications where we need to fast forward, and rewind time. Something we use is a Clock... in our context specification base class we reset the clock after each specification.

   public class Clock

   {

       static Clock()

       {

           Reset();

       }

       public static DateTime Now()

       {

           return current_time_provider();

       }

       public static Date Today()

       {

           return current_time_provider();

       }

       public static void ChangeTimeProviderTo(Func<DateTime> new_time_provider)

       {

           current_time_provider = new_time_provider;

       }

       public static void Reset()

       {

           current_time_provider = default_time_provider;

       }

       private static Func<DateTime> current_time_provider;

       private static readonly Func<DateTime> default_time_provider = () => DateTime.Now;

   }

# re: Test Helpers and Fluent Interfaces

Saturday, November 29, 2008 3:03 PM by Sean Feldman

Mo,

thanks for showing the Clock. We evaluated that option as well. One of the questions that was raised is should we be able to perform a reset on the object when it goes into the production code? The possibility of accidentally exploiting that option sounded not pleasant, and to obey the theory "if process is right, developer won't do many mistakes" decided to lock it to an immutable object.

Saying this, I really like the idea of setting Now() to a Func<DateTime>. One option I can see right away is moving away is to have a provider that can Reset(), but that behavior is not a part of the public contract for Clock, and tests would cast to that particular provider that has the reset. That way tests can be using reset, but production code (that is getting Clock through the static gateway) will not be able to Reset().

Appreciate your idea - thanks! :)

# C# 3.0 + R# = Great Tests Readability

Sunday, November 30, 2008 12:13 AM by sfeldman.NET

C# 3.0 has introduced lots of great features to make our life easier and syntax sweeter. Lots of people