I'm currently abstracting the concept of timers so that my classes that need one can use mock timers in tests or different implementations in operative mode (eg threadpool timers, thread-affine timers, etc.). Therefore, I created this interface:
public interface ITimer : IDisposable
{
bool IsEnabled { get; }
bool IsAutoResetting { get; set; }
TimeSpan Interval { get; set; }
void Start();
void Stop();
event EventHandler IntervalElapsed;
}
Now I want to create a wrapper that adapts the System.Threading.Timer
class and implements that interface. I want to do it using test-driven development. My class currently looks somewhat like this:
public sealed class ThreadPoolTimer : ITimer
{
private readonly Timer _timer;
public bool IsEnabled { get; private set; }
public bool IsAutoResetting { get; set; }
public TimeSpan Interval { get; set; }
public ThreadPoolTimer()
{
Interval = this.GetDefaultInterval();
_timer = new Timer(OnTimerCallback);
}
public void Dispose()
{
_timer.Dispose();
}
public void Start()
{
}
public void Stop()
{
}
private void OnTimerCallback(object state)
{
OnIntervalElapsed();
}
public event EventHandler IntervalElapsed;
private void OnIntervalElapsed()
{
var handler = IntervalElapsed;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
My actual question is: how would you write unit tests that describe the (soft real-time) requirements for the behavior of Start
, Stop
and IntervalElapsed
?
In my opinion, i should use eg an AutoResetEvent
and check if the event is raised within a certain timespan (maybe +/- 3ms). But writing that code somewhat violates the DAMP (descriptive and meaningful phrases) principle, I think. Is there an easier way to do this?
Should I make the dependency to System.Threading.Timer
external and then maybe use a shim for testing purposes? Unfortunately, the .NET timers do not have a common interface (which would make my work obsolete...)
What are your thoughts on that topic? Is there any documentation that I have not found yet and that I should read?
Sorry for having actually more than one question in this post, but this testing of soft real-time requirements is quite interesting, I think.
As no one answered this question yet, I'll tell you how I approached the problem: I used the spy pattern to actually implement the code that observes the behavior of the timer. The class looks like this:
public class ThreadPoolTimerSpy : IDisposable
{
private readonly ThreadPoolTimer _threadPoolTimer;
private int _intervalElapsedCallCount;
private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false);
public int NumberOfIntervals { get; set; }
public DateTime StartTime { get; private set; }
public DateTime EndTime { get; private set; }
public ThreadPoolTimerSpy(ThreadPoolTimer threadPoolTimer)
{
if (threadPoolTimer == null) throw new ArgumentNullException("threadPoolTimer");
_threadPoolTimer = threadPoolTimer;
_threadPoolTimer.IntervalElapsed += OnIntervalElapsed;
NumberOfIntervals = 1;
}
public void Measure()
{
_intervalElapsedCallCount = 0;
_resetEvent.Reset();
StartTime = DateTime.Now;
_threadPoolTimer.Start();
_resetEvent.WaitOne();
}
private void OnIntervalElapsed(object sender, EventArgs arguments)
{
_intervalElapsedCallCount++;
if (_intervalElapsedCallCount < NumberOfIntervals)
return;
_threadPoolTimer.Stop();
EndTime = DateTime.Now;
_resetEvent.Set();
}
public void Dispose()
{
_threadPoolTimer.Dispose();
_resetEvent.Dispose();
}
}
This class takes a ThreadPoolTimer
and registers to its IntervalElapsed
event. One can specify how much intervals the spy should wait until it stops measuring. As I'm using a ManualResetEvent
to block the thread that starts the timer in the Measure
method, all calls to that method are synchronous, which results in DAMP code in the actual test class, in my opinion.
A test method that uses the spy would look like this:
[TestInitialize]
public void InitializeTestEnvironment()
{
_testTarget = new ThreadPoolTimerBuilder().WithAutoResetOption(true)
.WithInterval(100)
.Build() as ThreadPoolTimer;
Assert.IsNotNull(_testTarget);
_spy = new ThreadPoolTimerSpy(_testTarget);
}
[TestMethod]
public void IntervalElapsedMustBeRaisedExactlyTenTimesAfter1000Milliseconds()
{
CheckIntervalElapsed(10, TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(100));
}
private void CheckIntervalElapsed(int numberOfIntervals, TimeSpan expectedTime, TimeSpan toleranceInterval)
{
_spy.NumberOfIntervals = numberOfIntervals;
_spy.Measure();
var passedTime = _spy.EndTime - _spy.StartTime;
var timingDifference = Math.Abs(expectedTime.Milliseconds - passedTime.Milliseconds);
Assert.IsTrue(timingDifference <= toleranceInterval.Milliseconds, string.Format("Timing difference: {0}", timingDifference));
}
If you have any questions or recommendations, please feel free to leave a comment.
Besides: the tolerance interval I have to choose to make the tests pass are relatively high. I thought that maybe 3 to 5 milliseconds might suffice, but in the end for ten intervals I figured out that the actual measured time span is up to 72ms different than the expected time of 1000ms in this case. Well, never use a managed runtime for real time applications, I guess...
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.