views:

116

answers:

3

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.

+2  A: 

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);
    }
}
Sjoerd
My stopwatch class uses .net's stopwatch class. I'd like to just make my class delegate its work to .net's one, as I wouldn't want to have to deal with all those dates and times if possible.
devoured elysium
I ended up implementing it in a similar way to you. I edited my OP.
devoured elysium
+1  A: 

One problem is that Stopwatch is not accurate on processors with variable clock speeds i.e. most current ones, as can be seen with the following discussion

http://stackoverflow.com/questions/3534957/c-stopwatch-shows-incorrect-time

So using Thread.Sleep wont give you an accurate time. You would be better to implement your own version of Stopwatch using DateTime.Now

Quinn351
I don't need great accuracy. The only reason for creating this interface is so I can later mock it/pass fake implementations.
devoured elysium
Ok, I initially found your post one of those "not really answering what was asked" posts but after reading your link you are right that I shouldn't rely on StopWatch. Thanks
devoured elysium
+2  A: 

Why would "artifially modifing a StopWatches' results"?! What's the whole purpose of testing if your going to modify your results?

As for testing you should also test the stop method and you should test if the amount of seconds is roughly 60 times more than the amount of minutes.

The class you are writing could also use StopWatch internally and then modify results as requested (then you can always go back to the original results).

PS: Method names should be shorter and not have underlines.

MrFox
"What's the whole purpose of testing if your going to modify your results?" The purpose is to accelerate/deaccelerate the stop watch when I need to.
devoured elysium
Also, I find it better to have long and descriptive names instead of short ones. For each one of the test methods, what could be shorter alternative names, in your opinion?
devoured elysium
I would use: TestZeroElapsedMinutes, TestOneElapsedSecond, TestOneElapsedSecond.So only the key words that identify the tests, you could even leave out 'Elapsed' because it's used in all methods.
MrFox
With that you are saying what you are testing, not the behaviour you are expecting the tests to have. How would you about assertions? I guess you'd have to make more than one assertion per test, then? There are definetively a lot of things to assert when zero minutes elapsed, when one second elapsed, etc?
devoured elysium