单元测试使用Timer的类

时间:2012-07-15 21:39:30

标签: c# unit-testing tdd

我有一个类,其私有成员的类型为System.Windows.Forms.Timer。每当我的计时器滴答时,也会调用一个私有方法。

  1. 是否值得测试该方法? (因为它是私人的)
  2. 我该如何测试? (我知道我的测试类可以继承我想要测试的类......)
  3. 我应该嘲笑我的计时器吗?因为如果我必须测试一个使用内部计时器的类,我的测试可能需要很长时间才能完成,对吧?
  4. 编辑:

    实际上,该方法依赖于时序,这里是代码:

    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()。这会是设计问题吗?

2 个答案:

答案 0 :(得分:36)

  1. 您没有测试方法(私人或公共) - 您正在验证班级的行为。如果你还没有验证某些行为,那么你不能说它已经实现了。有几种方法可以调用此行为 - 您的类的公共接口,或依赖的某些事件。行为调用也不一定会改变公共接口所达到的内容,与依赖项的交互也很重要。
  2. 请参阅下面的示例 - 它显示了如何测试此类“隐藏”行为。
  3. 请参阅下面的示例 - 它显示了如何分割职责,注入依赖关系并模拟它们。
  4. 实际上你的班级有太多的责任 - 一个是安排一些任务,另一个是 - 执行一些行动。尝试使用single responsibilities将您的班级分成两个单独的班级。

    因此,调度进入调度程序:)调度程序的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或其他方式。

答案 1 :(得分:23)

  

是否值得测试该方法? (因为它是私人的)

您的目的是确定您的代码是否有效。即使它是一个私有方法,它也应该生成一个公共接口可以达到的输出。您应该以用户可以知道它是否正常工作的方式设计您的课程。

此外,当您进行单元测试时,如果您可以模拟计时器,则可以访问分配给计时器的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
    }
}
  

我应该嘲笑我的计时器吗?因为如果我必须测试一个类   使用内部计时器,我的测试可能需要很长时间才能完成,   正确?

当然你应该嘲笑你的计时器。您的单元测试不能依赖于系统时间。您应该通过模拟来引发事件并查看代码的行为。