[英]Unit testing a class that uses a Timer
我有一個具有私有成員的類,該私有成員具有類型System.Windows.Forms.Timer
。 每當我的計時器計時時,還會調用一個私有方法。
編輯:
實際上,該方法依賴於計時,這是代碼:
private void alertTick(object sender, EventArgs e) {
if (getRemainingTime().Seconds <= 0) {
Display.execute(Name, WarningState.Ending, null);
AlertTimer.Stop();
}
else {
var warning = _warnings.First(x => x == getRemainingTime());
if (warning.TotalSeconds > 0)
Display.execute(Name, WarningState.Running, warning);
}
}
如您所見,如果計時器正在運行,它將使用與結束時(剩余時間等於0)不同的參數調用Display.execute()
)。 那會是設計上的問題嗎?
實際上,您的班級承擔了太多的責任-一個是安排一些任務,另一個是執行一些動作。 嘗試將您的班級分為兩個單獨的班級,並擔負單一責任 。
因此,調度到調度程序:)調度程序的API可能類似於:
public interface IScheduler
{
event EventHandler<SchedulerEventArgs> Alarm;
void Start();
void Stop();
}
現在忘記調度程序。 返回並實現您的第二堂課,這將顯示一些警告。 讓我們先測試(使用Moq):
[Test]
public void ShouldStopDisplayingWarningsWhenTimeIsOut()
{
Mock<IDisplay> display = new Mock<IDisplay>();
Mock<IScheduler> scheduler = new Mock<IScheduler>();
Foo foo = new Foo("Bar", scheduler.Object, display.Object);
scheduler.Raise(s => s.Alarm += null, new SchedulerEventArgs(0));
display.Verify(d => d.Execute("Bar", WarningState.Ending, null));
scheduler.Verify(s => s.Stop());
}
編寫實現:
public class Foo
{
private readonly IScheduler _scheduler;
private readonly IDisplay _display;
private readonly string _name;
public Foo(string name, IScheduler scheduler, IDisplay display)
{
_name = name;
_display = display;
_scheduler = scheduler;
_scheduler.Alarm += Scheduler_Alarm;
_scheduler.Start();
}
private void Scheduler_Alarm(object sender, SchedulerEventArgs e)
{
_display.Execute(_name, WarningState.Ending, null);
_scheduler.Stop();
}
}
測試通過。 寫另一個:
[Test]
public void ShouldNotStopDisplayingWarningsWhenTimeRemains()
{
Mock<IDisplay> display = new Mock<IDisplay>(MockBehavior.Strict);
Mock<IScheduler> scheduler = new Mock<IScheduler>(MockBehavior.Strict);
scheduler.Setup(s => s.Start());
Foo foo = new Foo("Bar", scheduler.Object, display.Object);
scheduler.Raise(s => s.Alarm += null, new SchedulerEventArgs(1));
}
測試失敗。 嗯,您需要剩余時間的條件:
private void Scheduler_Alarm(object sender, SchedulerEventArgs e)
{
if (e.RemainingTime > 0)
return;
_display.Execute(_name, WarningState.Ending, null);
_scheduler.Stop();
}
您可以繼續為您的班級編寫測試,這些測試負責處理調度程序警報並在顯示時執行一些警告。 完成后,您可以為IScheduler
接口編寫實現。 通過System.Windows.Forms.Timer或System.ThreadingTimer或其他方式實現調度無關緊要。
是否值得測試該方法? (因為它是私人的)
您的目的是確定您的代碼是否有效。 即使是私有方法,它也應該生成公共接口可以訪問的輸出。 您應該以用戶可以知道其是否正常工作的方式設計類。
同樣,在進行單元測試時,如果可以模擬計時器,則可以訪問分配給計時器的Elapsed事件的回調。
我該如何測試? (我知道我可以讓我的測試類繼承要測試的類...)
您可以在此處使用適配器類。 首先,由於Timer類不提供抽象,因此您必須定義一個抽象。
public interface ITimer
{
void Start();
void Stop();
double Interval { get; set; }
event ElapsedEventHandler Elapsed;
//and other members you need
}
然后,您可以在適配器類中實現此接口,而僅從Timer類繼承即可。
public class TimerAdaper : Timer, ITimer { }
您應該將抽象注入構造函數(或作為屬性),以便可以在測試中對其進行模擬。
public class MyClass
{
private readonly ITimer _timer;
public MyClass(ITimer timer)
{
_timer = timer
}
}
我應該嘲笑我的計時器嗎? 因為如果我必須測試使用內部計時器的類,那么我的測試可能會花費很多時間才能完成,對嗎?
當然,您應該嘲笑您的計時器。 您的單元測試不能取決於系統時間。 您應該通過模擬引發事件,並查看代碼的行為。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.