简体   繁体   English

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

[英]How to unit test a timer class (adapter pattern)?

I'm currently abstracting the concept of timers so that my classes that need one can use mock timers in tests or different implementations in operative mode (eg threadpool timers, thread-affine timers, etc.). 我目前正在抽象定时器的概念,以便我需要的定时器可以在测试中使用模拟定时器或在操作模式下使用不同的实现(例如,线程池定时器,线程仿射定时器等)。 Therefore, I created this interface: 因此,我创建了这个界面:

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

    void Start();
    void Stop();

    event EventHandler IntervalElapsed;
}

Now I want to create a wrapper that adapts the System.Threading.Timer class and implements that interface. 现在我想创建一个包装器来适应System.Threading.Timer类并实现该接口。 I want to do it using test-driven development. 我想用测试驱动开发来做。 My class currently looks somewhat like this: 我的班级目前看起来有点像这样:

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);
    }
}

My actual question is: how would you write unit tests that describe the (soft real-time) requirements for the behavior of Start , Stop and IntervalElapsed ? 我的实际问题是:您如何编写单元测试来描述StartStopIntervalElapsed行为的(软实时)要求?

In my opinion, i should use eg an AutoResetEvent and check if the event is raised within a certain timespan (maybe +/- 3ms). 在我看来,我应该使用例如AutoResetEvent并检查事件是否在某个时间跨度内提升(可能是+/- 3ms)。 But writing that code somewhat violates the DAMP (descriptive and meaningful phrases) principle, I think. 但我认为,编写该代码有点违反了DAMP(描述性和有意义的短语)原则。 Is there an easier way to do this? 有更简单的方法吗?

Should I make the dependency to System.Threading.Timer external and then maybe use a shim for testing purposes? 我是否应该依赖于System.Threading.Timer外部,然后可能使用垫片进行测试? Unfortunately, the .NET timers do not have a common interface (which would make my work obsolete...) 不幸的是,.NET计时器没有通用接口(这会使我的工作过时......)

What are your thoughts on that topic? 您对该主题有何看法? Is there any documentation that I have not found yet and that I should read? 有没有我尚未找到的文件,我应该阅读?

Sorry for having actually more than one question in this post, but this testing of soft real-time requirements is quite interesting, I think. 很抱歉在这篇文章中实际上有多个问题,但我认为这种软实时要求测试非常有趣。

As no one answered this question yet, I'll tell you how I approached the problem: I used the spy pattern to actually implement the code that observes the behavior of the timer. 由于还没有人回答这个问题,我会告诉你我是如何解决问题的:我使用间谍模式来实际实现观察计时器行为的代码。 The class looks like this: 这个类看起来像这样:

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();
    }
}

This class takes a ThreadPoolTimer and registers to its IntervalElapsed event. 该类接受ThreadPoolTimer并注册到其IntervalElapsed事件。 One can specify how much intervals the spy should wait until it stops measuring. 可以指定间谍应该等待多长时间才能停止测量。 As I'm using a ManualResetEvent to block the thread that starts the timer in the Measure method, all calls to that method are synchronous, which results in DAMP code in the actual test class, in my opinion. 由于我正在使用ManualResetEvent来阻止在Measure方法中启动计时器的线程,因此对该方法的所有调用都是同步的,在我看来,这导致实际测试类中的DAMP代码。

A test method that uses the spy would look like this: 使用间谍的测试方法如下所示:

[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));
}

If you have any questions or recommendations, please feel free to leave a comment. 如果您有任何问题或建议,请随时发表评论。

Besides: the tolerance interval I have to choose to make the tests pass are relatively high. 此外:我必须选择使测试通过的公差间隔相对较高。 I thought that maybe 3 to 5 milliseconds might suffice, but in the end for ten intervals I figured out that the actual measured time span is up to 72ms different than the expected time of 1000ms in this case. 我认为可能3到5毫秒就足够了,但最后10个时间间隔我发现实际测量的时间跨度比这种情况下1000ms的预期时间长达72ms。 Well, never use a managed runtime for real time applications, I guess... 好吧,我从不使用托管运行时用于实时应用程序...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM