0

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.

devoured elysium
  • 101,373
  • 131
  • 340
  • 557
  • Never use date time requests for elapsed time determination. It has less accuracy than `Stopwatch` and should never be done. Plus if daylight savings time changes you could have +/- an hour on either time value. Not to mention that Windows can change the clock speed to gradually adjust the clock to match an external time source. – Jeremy Nov 27 '17 at 18:05

4 Answers4

3

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
  • 4,852
  • 7
  • 45
  • 81
  • 1
    "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 Aug 22 '10 at 11:11
  • 1
    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 Aug 22 '10 at 13:14
  • 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 Aug 24 '10 at 10:34
  • 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 Aug 29 '10 at 17:03
2

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
  • 74,049
  • 16
  • 131
  • 175
  • 1
    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 Aug 22 '10 at 11:12
  • I ended up implementing it in a similar way to you. I edited my OP. – devoured elysium Aug 22 '10 at 19:22
  • 1
    Please don't do this! `Stopwatch` is based on the QPC on the Windows OS which is still far better than using `DateTime` which can change for any reason (user edit/time sync/DST change). – Jeremy Nov 27 '17 at 18:13
  • @sjoerd if you want to use `DateTime` which I don't recommend, at least use `DateTime.UtcNow` which is not affected by DST changes. It is still impacted by user changes and time sync though so I don't recommend the use of such methods. Personally I have never encountered the issues with the `Stopwatch` that others have, but I attribute such issues to a faulty BIOS or failure of QPC to properly detect whether the TSC is valid to use (OS bug). These should be reported. – Jeremy Dec 29 '17 at 15:45
1

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

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

Community
  • 1
  • 1
Jason Quinn
  • 2,443
  • 3
  • 28
  • 36
  • 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 Aug 22 '10 at 11:10
  • 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 Aug 22 '10 at 11:22
0

As an aside to the EDIT above: Do not use this method to determine elapsed time! It does not work well when:

  • List item
  • the clock or timezone is being set
  • daylight savings starts or ends
  • the clock is otherwise unreliable (e.g. inside a VM)

... and probably a number of other conditions.