Sunday, January 19, 2014

Unit testing code with timers

Attempts to unit test code which uses a timer most often lead to tests like:

[Test]
public void SlowAndFragileTest()
{
    // Setup
    dataProviderMock = MockRepository.
                             GenerateMock<IDataProvider>();
    systemUnderTest = new PollCommand(dataProviderMock);

    // Act
    systemUnderTest.StartPoll();

    // Make sure the timer has been executed at least
    // once. The action is started after 10 seconds.
    Thread.Sleep(11000);

    // Assert
    dataProviderMock .AssertWasCalled(
                         x=>x.Retrieve());
}

This testing approach is problematic. The test always takes 11 seconds, which is far to slow for a unit test. Depending on the timer used it is not guaranteed at what exact point in time the action is triggered. The action itself also takes time, so we can't be 100% sure that the action is finished when we test reaches the assert statement.

Solution - Test code with timers in one thread

We can separate the timer mechanics from our code and use a special timer which executes the action on the current thread. We can also tell this timer how much time has passed by:
[Test]
public void ShouldRetrieveDataEvery10Seconds()
{
    // Arrange
    timerStub = new DeterministicTimer();
    dataProviderMock = MockRepository.
                             GenerateMock<IDataProvider>();
    systemUnderTest = new PollCommand(
                             timerStub, 
                             dataProviderMock);

    // Act
    systemUnderTest.StartPoll();
    // Now execute the timer actions on
    // the current thread.
    timerStub.TickSeconds(30);

    // Assert
    dataProviderMock.AssertWasCalled(
                        x=>x.Retrieve(),
                        y=>y.Repeat.Times(3));
}

This test is very fast and takes fractions of a second. This test is also robust because every code is executed on the current thread.
Our PollComand class we are testing now looks very simple:
private readonly ITimer timer;

public void StartPoll()
{
    timer.StartTimer(
             RetrieveData, 
             new TimeSpan(0, 0, 0, 10));
}
private void RetrieveData()
{
    retrievedDate = dataProvider.Retrieve();
}

The DeterministicTimer is straight forward:
public class DeterministicTimer : ITimer
{
 private Action action;
 private TimeSpan intervall;

 private bool isStarted;
 private TimeSpan elapsedTime;

 #region ITimer methods
 public void StartTimer(Action action, TimeSpan intervall)
 {
     isStarted = true;
     this.action = action;
     this.intervall = intervall;
     elapsedTime = new TimeSpan();
 }

 public bool IsStarted()
 {
     return isStarted;
 }

 public void StopTimer()
 {
     isStarted = false;
 }
 #endregion

 public void TickSeconds(int seconds)
 {
     TimeSpan newElapsedTime = elapsedTime + new TimeSpan(0, 0, 0, seconds);
     if (isStarted)
     {
       long executionCountBefore = elapsedTime.Ticks / intervall.Ticks;
       long executionCountAfter = newElapsedTime.Ticks / intervall.Ticks;

       for (int i = 0; i < executionCountAfter - executionCountBefore; i++)
       {
         action();
       }
     }
     elapsedTime = newElapsedTime;
 }
}
See also my MSDN Magazine article for general remarks about unit testing multithreaded code.

No comments: