如何在 Threading.Timer 周围制作可测试的包装器并在单元测试时替换它?
How to make a testable wrapper around Threading.Timer and replace it when unit testing?
我已经为 System.Threading.Timer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period);
过载测试苦苦挣扎了一天多。
基于 this solution 我创建了 ThreadingTimer、FakeTimer,它实现了 ITimer:
public interface ITimer
{
bool Change(int dueTime, int period);
bool IsDisposed { get; }
void Dispose();
}
public class ThreadingTimer : ITimer, IDisposable
{
private Timer _timer;
public ThreadingTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
{
_timer = new Timer(callback, state, dueTime, period);
}
public bool IsDisposed { get; set; }
public void Dispose()
{
IsDisposed = true;
Dispose();
GC.SuppressFinalize(this);
}
public bool Change(int dueTime, int period)
{
_timer.Change(dueTime, period);
return true;
}
}
public class FakeTimer : ITimer
{
private object state;
public FakeTimer(TimerCallback callback, object state)
{
this.state = state;
}
public bool IsDisposed { get; set; }
public bool Change(int dueTime, int period)
{
return true;
}
public void Dispose()
{
IsDisposed = true;
Dispose();
GC.SuppressFinalize(this);
}
}
在我的服务中 class 我想使用我的 ThreadingTimer:
public ITimer Timer { get; set; }
private void StartUpdates()
{
Task.Run(() =>
{
Timer = new ThreadingTimer(StartUpdatingVessels, null, TimeSpan.Zero, TimeSpan.FromHours(24));
}, Token);
}
但是当涉及到单元测试时,我不知道也不了解如何在我的 FakeTimer[=37 中使用和利用 ITimer 的实现=].因为如果在我的测试中将调用方法 StartUpdates()
,将创建 ThreadingTimer 的新实例,即使已经分配了 Timer prop 假定时器:
[Fact]
public void SetUpdatingStarted_CalledWhenUpdatingAlreadyStarted_DisposesTimer()
{
_timedUpdateControl.Timer = new FakeTimer(CallbackTestMethod, null);
bool intendedToStartUpdates = false;
_timedUpdateControl.StartOrStopUpdates(intendedToStartUpdates); //that methid calls private method StartUpdates() and creates instance for ITimer
//assert something
}
我怎么在那里嘲笑它? (我在测试项目中使用 Moq 框架)。
一种解决方案是使用依赖注入:
public interface ITimer
{
event Action Elapsed;
void StartTimer(int dueTime, int period);
bool IsDisposed { get; }
void Dispose();
}
public class TimedUpdateControl
{
private ITimer timer;
public TimedUpdateControl(ITimer timer)
{
this.timer = timer;
this.timer.Elapsed += () => { // do something }
}
private void StartUpdates()
{
Task.Run(() =>
{
timer.StartTimer(TimeSpan.Zero, TimeSpan.FromHours(24));
}, Token);
}
}
测试:
[Fact]
public void SetUpdatingStarted_CalledWhenUpdatingAlreadyStarted_DisposesTimer()
{
var timedUpdateControl = new TimedUpdateControl(new FakeTimer());
bool intendedToStartUpdates = false;
timedUpdateControl.StartOrStopUpdates(intendedToStartUpdates);
//assert something
}
我已经为 System.Threading.Timer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period);
过载测试苦苦挣扎了一天多。
基于 this solution 我创建了 ThreadingTimer、FakeTimer,它实现了 ITimer:
public interface ITimer
{
bool Change(int dueTime, int period);
bool IsDisposed { get; }
void Dispose();
}
public class ThreadingTimer : ITimer, IDisposable
{
private Timer _timer;
public ThreadingTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
{
_timer = new Timer(callback, state, dueTime, period);
}
public bool IsDisposed { get; set; }
public void Dispose()
{
IsDisposed = true;
Dispose();
GC.SuppressFinalize(this);
}
public bool Change(int dueTime, int period)
{
_timer.Change(dueTime, period);
return true;
}
}
public class FakeTimer : ITimer
{
private object state;
public FakeTimer(TimerCallback callback, object state)
{
this.state = state;
}
public bool IsDisposed { get; set; }
public bool Change(int dueTime, int period)
{
return true;
}
public void Dispose()
{
IsDisposed = true;
Dispose();
GC.SuppressFinalize(this);
}
}
在我的服务中 class 我想使用我的 ThreadingTimer:
public ITimer Timer { get; set; }
private void StartUpdates()
{
Task.Run(() =>
{
Timer = new ThreadingTimer(StartUpdatingVessels, null, TimeSpan.Zero, TimeSpan.FromHours(24));
}, Token);
}
但是当涉及到单元测试时,我不知道也不了解如何在我的 FakeTimer[=37 中使用和利用 ITimer 的实现=].因为如果在我的测试中将调用方法 StartUpdates()
,将创建 ThreadingTimer 的新实例,即使已经分配了 Timer prop 假定时器:
[Fact]
public void SetUpdatingStarted_CalledWhenUpdatingAlreadyStarted_DisposesTimer()
{
_timedUpdateControl.Timer = new FakeTimer(CallbackTestMethod, null);
bool intendedToStartUpdates = false;
_timedUpdateControl.StartOrStopUpdates(intendedToStartUpdates); //that methid calls private method StartUpdates() and creates instance for ITimer
//assert something
}
我怎么在那里嘲笑它? (我在测试项目中使用 Moq 框架)。
一种解决方案是使用依赖注入:
public interface ITimer
{
event Action Elapsed;
void StartTimer(int dueTime, int period);
bool IsDisposed { get; }
void Dispose();
}
public class TimedUpdateControl
{
private ITimer timer;
public TimedUpdateControl(ITimer timer)
{
this.timer = timer;
this.timer.Elapsed += () => { // do something }
}
private void StartUpdates()
{
Task.Run(() =>
{
timer.StartTimer(TimeSpan.Zero, TimeSpan.FromHours(24));
}, Token);
}
}
测试:
[Fact]
public void SetUpdatingStarted_CalledWhenUpdatingAlreadyStarted_DisposesTimer()
{
var timedUpdateControl = new TimedUpdateControl(new FakeTimer());
bool intendedToStartUpdates = false;
timedUpdateControl.StartOrStopUpdates(intendedToStartUpdates);
//assert something
}