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です。 、Stopおよびの動作の (ソフト リアルタイム) 要件を記述する単体テストをどのように記述しIntervalElapsedますか?

私の意見では、たとえば anAutoResetEventを使用して、イベントが特定の期間 (おそらく +/- 3ms) 内に発生したかどうかを確認する必要があります。しかし、そのコードを書くことは、DAMP (記述的で意味のあるフレーズ) の原則に多少違反していると思います。これを行う簡単な方法はありますか?

外部への依存関係を作成してからSystem.Threading.Timer、テスト目的でシムを使用する必要がありますか? 残念ながら、.NET タイマーには共通のインターフェイスがありません (私の作業は時代遅れになります...)。

そのトピックについてどう思いますか。まだ見つけていない、読むべきドキュメントはありますか?

この投稿で複数の質問をして申し訳ありませんが、このソフト リアルタイム要件のテストは非常に興味深いものだと思います。

4

1 に答える 1

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

このクラスは を取り、ThreadPoolTimerそのIntervalElapsedイベントに登録します。スパイが測定を停止するまで待機する間隔を指定できます。ManualResetEventメソッドでタイマーを開始するスレッドをブロックするためにを使用しているため、その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));
}

ご質問やご提案がありましたら、お気軽にコメントを残してください。

その上、テストに合格するために選択しなければならない許容間隔は比較的高いです。3 ミリ秒から 5 ミリ秒で十分かもしれないと考えましたが、最終的に 10 間隔で、実際に測定されたタイム スパンは、この場合の 1000 ミリ秒の予想時間とは最大 72 ミリ秒異なることがわかりました。ええと、リアルタイム アプリケーションにマネージド ランタイムを使用しないでください...

于 2013-06-06T08:29:34.923 に答える