Search code examples
c#unit-testingtestingtddtest-first

How to develop a StopWatch class test first?


I'm currently trying to implement a StopWatch class. The interface is something like:

interface IStopWatch {
    void Run();
    void Stop();
    int SecondsElapsed { get; }
    int MinutesElapsed { get; }
}

Basically my app will need to use a StopWatch, but for testing purposes it'd be nice to have a way of artifially modifing a StopWatches' results, so that is the reason I am making all my code reference an IStopWatch instead of .NET's System.Stopwatch.

As I'm trying to develop this Test-First, I'll have to make code for my StopWatch class only after writing its tests. I already realized that the tests I'm going to do aren't going to be Unit-Tests, as I'm using .NET's System.Stopwatch class internally.

So, in my undertanding, the only thing I can do now are tests that look like the following:

[TestMethod]
public void Right_After_Calling_Run_Elapsed_Minutes_Equals_Zero() {
    IStopWatch stopWatch = new StopWatch();
    stopWatch.Run();
    Assert.AreEqual<int>(0, stopWatch.ElapsedMinutes);
}

[TestMethod]
public void One_Second_After_Run_Elapsed_Seconds_Equals_One() {
    IStopWatch stopWatch = new StopWatch();
    stopWatch.Run();
    Thread.Sleep(1000 + 10);
    Assert.AreEqual<int>(1, stopWatch.ElapsedSeconds);
}

[TestMethod]
public void Sixty_Seconds_After_Run_Elapsed_Minutes_Equals_One() {
    IStopWatch stopWatch = new StopWatch();
    stopWatch.Run();
    Thread.Sleep(60 * 1000 + 1000);
    Assert.AreEqual<int>(1, stopWatch.ElapsedMinutes);            
}

I know I can't just run this as often as my Unit-Tests, but I think there is nothing I can do about this. Is my approach correct or am I missing something?

Thanks

Edit

So I ended up following both Quinn351 and Sjoerd's advices and coded something that uses DateTimes instead of .NET's Stopwatch class:

public class MyStopWatch : IMyStopWatch {
    private DateTime? firstDateTime = null;
    private DateTime? secondDateTime = null;
    private bool isRunning = false;

    public void Start() {
        isRunning = true;
        firstDateTime = DateTime.Now;
    }

    public void Stop() {
        isRunning = false;
        secondDateTime = DateTime.Now;
    }

    public void Reset() {
        firstDateTime = null;
        secondDateTime = null;
        isRunning = false;
    }

    public TimeSpan GetTimeElapsed() {
        return secondDateTime.Value.Subtract(firstDateTime.Value);
    }
}

This allows me to make other implementations that have getter/setters for both dates so I can make GetTimeElapsed() return whatever I want.


Solution

  • Your stopwatch class probably gets the date and time from somewhere. Make an object which returns the current date. This would be a very simple class. When testing, pass in a mock object instead, which returns a fake date and time. This way, you can pretend in your test as if many seconds have elapsed.

    class DateTime {
        public Date getDate() {
            return System.DateTime.Now();
        }
    }
    
    class MockDateTime {
        public Date getDate() {
            return this.fakeDate;
        }
    }
    
    class StopwatchTest {
        public void GoneInSixtySeconds() {
            MockDateTime d = new MockDateTime();
            d.fakeDate = '2010-01-01 12:00:00';
            Stopwatch s = new Stopwatch();
            s.Run();
            d.fakeDate = '2010-01-01 12:01:00';
            s.Stop();
            assertEquals(60, s.ElapsedSeconds);
        }
    }