Pac*_*ane 35 c# tdd unit-testing
我有一个类有私有成员的类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);
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,如果计时器正在运行,它将Display.execute()从结束时(当剩余时间等于0时)使用不同的参数进行调用.这会是设计问题吗?
Ser*_*kiy 36
实际上你的班级有太多的责任 - 一个是安排一些任务,另一个是 - 执行一些行动.尝试将您的班级分成两个单独职责的单独班级.
所以,调度去调度程序:)调度程序的API可能是这样的:
public interface IScheduler
{
event EventHandler<SchedulerEventArgs> Alarm;
void Start();
void Stop();
}
Run Code Online (Sandbox Code Playgroud)
暂时忘掉调度程序.返回并实现您的第二个类,它将显示一些警告.让我们先测试一下(使用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());
}
Run Code Online (Sandbox Code Playgroud)
写实施:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
测试通过.写另一个:
[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));
}
Run Code Online (Sandbox Code Playgroud)
测试失败.啊,你需要剩余时间的条件:
private void Scheduler_Alarm(object sender, SchedulerEventArgs e)
{
if (e.RemainingTime > 0)
return;
_display.Execute(_name, WarningState.Ending, null);
_scheduler.Stop();
}
Run Code Online (Sandbox Code Playgroud)
您可以继续为您的类编写测试,该测试负责处理调度程序警报并在显示时执行某些警告.完成后,您可以为您的IScheduler界面编写实现.无论如何实现调度 - 通过System.Windows.Forms.Timer或通过System.ThreadingTimer或其他方式.
Ufu*_*arı 24
是否值得测试该方法?(因为它是私人的)
您的目的是确定您的代码是否有效.即使它是一个私有方法,它也应该生成一个公共接口可以达到的输出.您应该以用户可以知道它是否正常工作的方式设计您的课程.
此外,当您进行单元测试时,如果您可以模拟计时器,则可以访问分配给计时器的Elapsed事件的回调.
我该怎么测试呢?(我知道我可以让我的测试类继承我要测试的类......)
您可以在此处使用适配器类.首先,您必须定义抽象,因为Timer类不提供抽象.
public interface ITimer
{
void Start();
void Stop();
double Interval { get; set; }
event ElapsedEventHandler Elapsed;
//and other members you need
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以在适配器类中实现此接口,只需继承Timer类.
public class TimerAdaper : Timer, ITimer { }
Run Code Online (Sandbox Code Playgroud)
您应该在构造函数中(或作为属性)注入您的抽象,以便您可以在测试中模拟它.
public class MyClass
{
private readonly ITimer _timer;
public MyClass(ITimer timer)
{
_timer = timer
}
}
Run Code Online (Sandbox Code Playgroud)
我应该嘲笑我的计时器吗?因为如果我必须测试一个使用内部计时器的类,我的测试可能需要很长时间才能完成,对吧?
当然你应该嘲笑你的计时器.您的单元测试不能依赖于系统时间.您应该通过模拟来引发事件并查看代码的行为方式.