5

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

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的(软实时)要求的单元测试?StopIntervalElapsed

在我看来,我应该使用例如 anAutoResetEvent并检查事件是否在某个时间跨度内引发(可能是 +/- 3 毫秒)。但我认为编写该代码有点违反 DAMP(描述性和有意义的短语)原则。有没有更简单的方法来做到这一点?

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

你对这个话题有什么看法?是否有任何我还没有找到并且我应该阅读的文档?

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

4

1 回答 1

0

由于还没有人回答这个问题,我将告诉你我是如何解决这个问题的:我使用 spy 模式来实际实现观察计时器行为的代码。该类如下所示:

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事件。可以指定间谍应该等待多少时间间隔,直到它停止测量。由于我使用 aManualResetEvent来阻止在方法中启动计时器的线程,因此对该Measure方法的所有调用都是同步的,在我看来,这会导致实际测试类中的 DAMP 代码。

使用 spy 的测试方法如下所示:

[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 毫秒就足够了,但最后十个间隔我发现实际测量的时间跨度与这种情况下的 1000 毫秒的预期时间相差最多 72 毫秒。好吧,我猜永远不要将托管运行时用于实时应用程序......

于 2013-06-06T08:29:34.923 回答