如何对计时器类(适配器模式)进行单元测试?

feO*_*O2x 5 .net c# tdd unit-testing real-time

我目前正在抽象定时器的概念,以便我需要的定时器可以在测试中使用模拟定时器或在操作模式下使用不同的实现(例如线程池定时器,线程仿射定时器等).因此,我创建了这个界面:

public interface ITimer : IDisposable
{
    bool IsEnabled { get; }
    bool IsAutoResetting { get; set; }
    TimeSpan Interval { get; set; }

    void Start();
    void Stop();

    event EventHandler IntervalElapsed;
}
Run Code Online (Sandbox Code Playgroud)

现在我想创建一个适应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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我的实际问题是:你如何编写描述(软实时)行为的单元测试Start,Stop以及IntervalElapsed

在我看来,我应该使用例如a AutoResetEvent并检查事件是否在某个时间跨度内提升(可能是+/- 3ms).但我认为,编写该代码有点违反了DAMP(描述性和有意义的短语)原则.有更简单的方法吗?

我是否应该依赖System.Threading.Timer外部,然后可能使用垫片进行测试?不幸的是,.NET计时器没有通用接口(这会使我的工作过时......)

您对该主题有何看法?有没有我尚未找到的文件,我应该阅读?

很抱歉在这篇文章中实际上有多个问题,但我认为这种软实时要求测试非常有趣.

feO*_*O2x 0

由于还没有人回答这个问题,我将告诉您我是如何解决这个问题的:我使用间谍模式来实际实现观察计时器行为的代码。该类看起来像这样:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

该类采用 aThreadPoolTimer并注册其IntervalElapsed事件。人们可以指定间谍程序在停止测量之前应等待的时间间隔。由于我使用 aManualResetEvent来阻止在方法中启动计时器的线程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));
}
Run Code Online (Sandbox Code Playgroud)

如果您有任何疑问或建议,请随时发表评论。

此外:我必须选择使测试通过的容差区间相对较高。我认为也许 3 到 5 毫秒就足够了,但最后十个间隔我发现实际测量的时间跨度与本例中 1000 毫秒的预期时间相差最多 72 毫秒。好吧,我想永远不要将托管运行时用于实时应用程序......