My AsyncDetector
is internally using a Stopwatch
to calculate time intervals. Because of this hardcoded dependency I am not able to write good unit-tests for it. So I must be able to use my own timestamps that I can use for assertions.
To do this I defined the IStopwatch
interface that is a simplified version of the Stopwatch
. Some of the properties I never use and my inteface does not provide them.
public interface IStopwatch
{
bool IsRunning { get; }
TimeSpan Elapsed { get; }
IStopwatch Start();
IStopwatch Stop();
IStopwatch Restart();
IStopwatch Reset();
}
Its default implementation is using of course the original Stopwatch
so I provide it just for reference:
public class SystemStopwatch : IStopwatch { private readonly Stopwatch _stopwatch; public SystemStopwatch() { _stopwatch = new Stopwatch(); } public bool IsRunning => _stopwatch.IsRunning; public TimeSpan Elapsed => _stopwatch.Elapsed; public static IStopwatch StartNew() { return new SystemStopwatch().Start(); } public IStopwatch Start() { _stopwatch.Start(); return this; } public IStopwatch Stop() { _stopwatch.Stop(); return this; } public IStopwatch Restart() { _stopwatch.Restart(); return this; } public IStopwatch Reset() { _stopwatch.Restart(); return this; } public override string ToString() { return _stopwatch.Elapsed.ToString(); } }
More interesing is the DebugStopwatch
that will enumerate my timestamps on each access to the Elapsed
property or it'll throw if there are not enough of them:
public class DebugStopwatch : IStopwatch
{
private readonly IEnumerable<TimeSpan> _timestamps;
private TimeSpan? _lastElapsed;
private IEnumerator<TimeSpan> _enumerator;
public DebugStopwatch([NotNull] IEnumerable<TimeSpan> elapses)
{
_timestamps = elapses ?? throw new ArgumentNullException(nameof(elapses));
}
public bool IsRunning { get; private set; }
public TimeSpan Elapsed
{
get
{
if (IsRunning)
{
if (_enumerator.MoveNext())
{
return (_lastElapsed = _enumerator.Current).Value;
}
throw new InvalidOperationException("You did not define enough timestamps.");
}
return _enumerator?.Current ?? _lastElapsed ?? TimeSpan.Zero;
}
}
public static IStopwatch StartNew(IEnumerable<TimeSpan> elapses)
{
return new DebugStopwatch(elapses).Start();
}
public IStopwatch Start()
{
if (IsRunning)
{
return this;
}
_enumerator = _timestamps.GetEnumerator();
IsRunning = true;
return this;
}
public IStopwatch Stop()
{
IsRunning = false;
return this;
}
public IStopwatch Restart()
{
Stop();
Reset();
Start();
return this;
}
public IStopwatch Reset()
{
_enumerator = _timestamps.GetEnumerator();
return this;
}
public override string ToString()
{
return (_lastElapsed ?? TimeSpan.Zero).ToString();
}
}
Example Using it is very easy. I just need to provide a collection or enumeration with timestamps:
void Main()
{
var stopwatch = DebugStopwatch.StartNew(Enumerable.Range(0, 10).Select(i => TimeSpan.FromSeconds(i)));
stopwatch.Dump();
stopwatch.Dump();
stopwatch.Dump();
stopwatch.Stop().Dump(nameof(IStopwatch.Stop));
stopwatch.Dump();
stopwatch.Dump();
stopwatch.Reset().Dump(nameof(IStopwatch.Reset));
stopwatch.Dump();
stopwatch.Dump();
}
What do you think of the DebugStopwatch
? Would you change anything about it? If so, how would you improve it?
Restart()
inReset()
. \$\endgroup\$