문제

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.

도움이 되었습니까?

해결책

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);
    }
}

다른 팁

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.

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

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.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top