wpf 如何对依赖于Dispatcher Timer的SUT进行单元测试

hivapdat  于 2023-05-08  发布在  其他
关注(0)|答案(2)|浏览(181)

三天来我一直在寻找解决方案。我有这篇文章,但不幸的是,它使用了一个不再可用的库(https://www.codeproject.com/Articles/19597/How-to-Test-a-Class-Which-Uses-DispatcherTimer)。所以我希望有一个新的解决方案,或者更优雅的方法来单元测试使用DispatcherTimer的类。
下面是我的SUT代码:

public class RemainingTimeChangedEventArgs : EventArgs
{
    public TimeSpan RemainingTime { get; init; }
    public TimeSpan TimerSetFor { get; init; }
}

public class ExtendedTimer
{
    public event EventHandler<RemainingTimeChangedEventArgs>? RemainingTimeChanged;
    private readonly DispatcherTimer _timer;
    private TimeSpan _timerSetFor  = TimeSpan.Zero;
    private TimeSpan _remainingTime  = TimeSpan.Zero;
    
    public TimeSpan Interval
    {
        get => _timer.Interval;
        set => _timer.Interval = value;
    }
    
    public ExtendedTimer()
    {
        _timer = new();
        _timer.Tick += OnTimeChanged;
        _timer.Interval = TimeSpan.FromSeconds(1);
    }

    public void Initialize(TimeSpan timerSetFor)
    {
        _timerSetFor = timerSetFor;
        _remainingTime = timerSetFor;
    }

    public void Start()
    {
        _timer.Start();
    }

    public void Resume()
    {
        _timer.Start();
    }

    public void Pause()
    {
        _timer.Stop();
    }
    public void Stop()
    {
        _timer.Stop();
    }

    private void OnTimeChanged(object? sender, EventArgs e)
    {
        _remainingTime -= Interval;

        if (_remainingTime == TimeSpan.Zero) _timer.Stop();

        RemainingTimeChanged?.Invoke(this, new RemainingTimeChangedEventArgs
        {
            RemainingTime = _remainingTime,
            TimerSetFor = _timerSetFor
        });
    }
    
    ~ExtendedTimer() {
        _timer.Tick -= OnTimeChanged;
    }

因此,当用户启动计时器并且TimerSetFor在给定时间内被初始化时,它将引发一个事件来告知每个tick/间隔的剩余时间。一旦剩余时间达到0,它将引发事件并停止计时。
目前,这是我的xUnit测试。我使用任务。延迟,它失败了,因为延迟任务意味着延迟调度器计时器的执行?但我希望它能揭示我的意图:

public class ExtendedTimerTests
{
    private readonly ExtendedTimer _sut = new();

    [Theory]
    [InlineData(10, 100, 10)]
    [InlineData(5, 50, 5)]
    [InlineData(20, 30, 1)]
    public async Task Start_GiveCorrectRemainingTimeEveryIntervalElapsed(
        int interval, int timerSetFor, int expectedExecutedFor)
    {
        _sut.Interval = TimeSpan.FromMilliseconds(interval);

        var ticksCount = 0;

        void OnSutOnRemainingTimeChanged(object? sender, RemainingTimeChangedEventArgs args)
        {
            ticksCount++;
        }

        _sut.RemainingTimeChanged += OnSutOnRemainingTimeChanged;

        _sut.Initialize(TimeSpan.FromMilliseconds(timerSetFor));
         _sut.Start();

         await Task.Delay(TimeSpan.FromMilliseconds(timerSetFor));
         Assert.Equal(expectedExecutedFor, ticksCount);

         // make sure the ticksCount still the same even after we wait for another time
         await Task.Delay(TimeSpan.FromMilliseconds(50));
         Assert.Equal(expectedExecutedFor, ticksCount);
    }

}
wtzytmuj

wtzytmuj1#

你发布的文章告诉你这个问题:
1.默认情况下,运行单元测试的线程没有活动的Dispatcher
你可以通过注入一个计时器实现来改变你的SUT(ExtendedTimer类),并且不要在你的单元测试中使用DispatcherTimer。

but5z9lq

but5z9lq2#

我结束了没有绑定到时间的单元测试,并将DispatcherTimer作为可以注入的依赖项。

public interface ITimer
{
    event EventHandler Tick;
    TimeSpan Interval { get; set; }
    void Start();
    void Stop();
}
public class DispatcherTimerAdapter : ITimer
{
    private readonly DispatcherTimer _timer = new();

    public event EventHandler? Tick
    {
        add => _timer.Tick += value;
        remove => _timer.Tick -= value;
    }

    public TimeSpan Interval
    {
        get => _timer.Interval;
        set => _timer.Interval = value;
    }

    public void Start()
    {
        _timer.Start();
    }

    public void Stop()
    {
        _timer.Stop();
    }
}
public class ExtendedTimer
{
...
  public ExtendedTimer(ITimer timer) // will be injected with DispatcherTimerAdapter
    {
        _timer = timer;
        _timer.Tick += OnTimeChanged;
        _timer.Interval = TimeSpan.FromSeconds(1);
    }
...
}

测试:

public class ExtendedTimerTests
{
    private readonly AutoMocker _mocker = new AutoMocker();
    private readonly Mock<ITimer> _timerMock;
    private readonly ExtendedTimer _sut;

    public ExtendedTimerTests()
    {
        _timerMock = _mocker.GetMock<ITimer>();
        _sut = _mocker.CreateInstance<ExtendedTimer>();
    }

    [Theory]
    [InlineData(1, 10, 10)]
    [InlineData(1, 5, 5)]
    [InlineData(2, 30, 15)]
    public void StartUntilTimerFinishItself_InvokedCorrectlyAndStopAutomatically(
        int intervalInSeconds, int timerSetForInSeconds, int expectedInvokedCount)
    {
        // Arrange
        GenerateTimerSetup(intervalInSeconds, timerSetForInSeconds);

        // Act
        int invokedCount = 0;
        int remainingTimeInSeconds = timerSetForInSeconds;

        _sut.RemainingTimeChanged += (sender, args) =>
        {
            invokedCount++;
            remainingTimeInSeconds = (int)args.RemainingTime.TotalSeconds;
        };

        _sut.Initialize(TimeSpan.FromSeconds(timerSetForInSeconds));
        _sut.Interval = TimeSpan.FromSeconds(intervalInSeconds);
        _sut.Start();

        // Assert
        _timerMock.Verify((x) => x.Stop(), Times.Once);
        Assert.Equal(0, remainingTimeInSeconds);
        Assert.Equal(expectedInvokedCount, invokedCount);
    }

    [Theory]
    [InlineData(1, 10, 5, 5, 5)]
    [InlineData(1, 5, 4, 4, 1)]
    [InlineData(2, 30, 10, 5, 20)]
    public void StartAndCaptureResultHalfway_InvokedCorrectly(
        int intervalInSeconds, 
        int timerSetForInSeconds, 
        int stopAt, 
        int expectedInvokedCount,
        int expectedRemainingTimeInSeconds
        )
    {
        // Arrange
        GenerateTimerSetup(intervalInSeconds, stopAt);

        // Act
        int invokedCount = 0;
        int remainingTimeInSeconds = timerSetForInSeconds;

        _sut.RemainingTimeChanged += (sender, args) =>
        {
            invokedCount++;
            remainingTimeInSeconds = (int)args.RemainingTime.TotalSeconds;
        };

        _sut.Initialize(TimeSpan.FromSeconds(timerSetForInSeconds));
        _sut.Interval = TimeSpan.FromSeconds(intervalInSeconds);
        _sut.Start();

        // Assert
        _timerMock.Verify((x) => x.Stop(), Times.Never);
        Assert.Equal(expectedRemainingTimeInSeconds, remainingTimeInSeconds);
        Assert.Equal(expectedInvokedCount, invokedCount);
    }

    #region Helpers

    private void GenerateTimerSetup(int intervalInSeconds, int stopAt)
    {
        _timerMock.Setup((x) => x.Start())
            .Callback(() =>
            {
                for (int i = 0; i < stopAt; i += intervalInSeconds)
                {
                    _timerMock.Raise((x) =>
                        x.Tick += null, _timerMock.Object, EventArgs.Empty);
                }
            });

        _timerMock.SetupGet((x) => x.Interval)
            .Returns(TimeSpan.FromSeconds(intervalInSeconds));

        _timerMock.Setup((x) => x.Stop());
    }

    #endregion

}

相关问题