[英]How to unit test a timer class (adapter pattern)?
我目前正在抽象定時器的概念,以便我需要的定時器可以在測試中使用模擬定時器或在操作模式下使用不同的實現(例如,線程池定時器,線程仿射定時器等)。 因此,我創建了這個界面:
public interface ITimer : IDisposable
{
bool IsEnabled { get; }
bool IsAutoResetting { get; set; }
TimeSpan Interval { get; set; }
void Start();
void Stop();
event EventHandler IntervalElapsed;
}
現在我想創建一個包裝器來適應System.Threading.Timer
類並實現該接口。 我想用測試驅動開發來做。 我的班級目前看起來有點像這樣:
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);
}
}
我的實際問題是:您如何編寫單元測試來描述Start
, Stop
和IntervalElapsed
行為的(軟實時)要求?
在我看來,我應該使用例如AutoResetEvent
並檢查事件是否在某個時間跨度內提升(可能是+/- 3ms)。 但我認為,編寫該代碼有點違反了DAMP(描述性和有意義的短語)原則。 有更簡單的方法嗎?
我是否應該依賴於System.Threading.Timer
外部,然后可能使用墊片進行測試? 不幸的是,.NET計時器沒有通用接口(這會使我的工作過時......)
您對該主題有何看法? 有沒有我尚未找到的文件,我應該閱讀?
很抱歉在這篇文章中實際上有多個問題,但我認為這種軟實時要求測試非常有趣。
由於還沒有人回答這個問題,我會告訴你我是如何解決問題的:我使用間諜模式來實際實現觀察計時器行為的代碼。 這個類看起來像這樣:
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();
}
}
該類接受ThreadPoolTimer
並注冊到其IntervalElapsed
事件。 可以指定間諜應該等待多長時間才能停止測量。 由於我正在使用ManualResetEvent
來阻止在Measure
方法中啟動計時器的線程,因此對該方法的所有調用都是同步的,在我看來,這導致實際測試類中的DAMP代碼。
使用間諜的測試方法如下所示:
[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));
}
如果您有任何問題或建議,請隨時發表評論。
此外:我必須選擇使測試通過的公差間隔相對較高。 我認為可能3到5毫秒就足夠了,但最后10個時間間隔我發現實際測量的時間跨度比這種情況下1000ms的預期時間長達72ms。 好吧,我從不使用托管運行時用於實時應用程序...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.