C# 对使用 Timer 的类进行单元测试
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11495830/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Unit testing a class that uses a Timer
提问by Pacane
I've got a class that has a private member that has for type System.Windows.Forms.Timer. There's also a private method that is being called every time my timer ticks.
我有一个类,它有一个具有 for type 的私有成员System.Windows.Forms.Timer。每次我的计时器滴答作响时,都会调用一个私有方法。
- Is it worth testing the method? (since it's private)
- How can I test it? (I know I can have my test class inheriting the class I want to test...)
- Should I be mocking my timer? Because if I have to test a class that uses an internal timer, my tests may take a lot of time to complete, right?
- 是否值得测试该方法?(因为它是私人的)
- 我该如何测试?(我知道我可以让我的测试类继承我想要测试的类......)
- 我应该嘲笑我的计时器吗?因为如果我必须测试一个使用内部计时器的类,我的测试可能需要很多时间才能完成,对吗?
edit:
编辑:
Actually, the method has a dependency on timing, here's the code:
实际上,该方法依赖于时间,代码如下:
private void alertTick(object sender, EventArgs e) {
if (getRemainingTime().Seconds <= 0) {
Display.execute(Name, WarningState.Ending, null);
AlertTimer.Stop();
}
else {
var warning = _warnings.First(x => x == getRemainingTime());
if (warning.TotalSeconds > 0)
Display.execute(Name, WarningState.Running, warning);
}
}
As you can see, if the timer is running, it calls Display.execute()with different parameters from when it's ending (when the remaining time equals 0). Would that be a problem of design?
如您所见,如果计时器正在运行,它将Display.execute()使用与结束时(剩余时间等于 0 时)不同的参数进行调用。会不会是设计的问题?
采纳答案by Sergey Berezovskiy
- You are not testing methods (private or public) - you are verifying behavior of your class. And if you have not verified some behavior, then you can't tell it was implemented. There are several ways this behavior could be invoked - public interface of your class, or some event in dependency. Also not necessary that behavior invocation will change something reached by the public interface, interactions with dependencies also matter.
- See example below - it shows how to test such "hidden" behavior.
- See example below - it shows how to split responsibilities, inject dependencies and mock them.
- 您不是在测试方法(私有或公共) - 您正在验证类的行为。如果您还没有验证某些行为,那么您就无法判断它已被实施。可以通过多种方式调用此行为 - 类的公共接口,或依赖的某些事件。行为调用也不一定会改变公共接口所达到的东西,与依赖项的交互也很重要。
- 请参阅下面的示例 - 它显示了如何测试此类“隐藏”行为。
- 请参见下面的示例 - 它展示了如何拆分职责、注入依赖项并模拟它们。
Actually your class have too many responsibilities - one is scheduling some task, and another - executing some actions. Try to split your class into two separate classes with single responsibilities.
实际上你的班级有太多的责任——一个是安排一些任务,另一个——执行一些动作。尝试将您的班级分成两个具有单一职责的独立班级。
So, scheduling goes to scheduler :) API of scheduler could be like:
所以,调度去调度器 :) 调度器的 API 可能是这样的:
public interface IScheduler
{
event EventHandler<SchedulerEventArgs> Alarm;
void Start();
void Stop();
}
Forget about scheduler for now. Return and implement your second class, which will display some warnings. Let's go test first (with Moq):
暂时忘记调度程序。返回并实现你的第二个类,它会显示一些警告。让我们先去测试(使用 Moq):
[Test]
public void ShouldStopDisplayingWarningsWhenTimeIsOut()
{
Mock<IDisplay> display = new Mock<IDisplay>();
Mock<IScheduler> scheduler = new Mock<IScheduler>();
Foo foo = new Foo("Bar", scheduler.Object, display.Object);
scheduler.Raise(s => s.Alarm += null, new SchedulerEventArgs(0));
display.Verify(d => d.Execute("Bar", WarningState.Ending, null));
scheduler.Verify(s => s.Stop());
}
Write implementation:
编写实现:
public class Foo
{
private readonly IScheduler _scheduler;
private readonly IDisplay _display;
private readonly string _name;
public Foo(string name, IScheduler scheduler, IDisplay display)
{
_name = name;
_display = display;
_scheduler = scheduler;
_scheduler.Alarm += Scheduler_Alarm;
_scheduler.Start();
}
private void Scheduler_Alarm(object sender, SchedulerEventArgs e)
{
_display.Execute(_name, WarningState.Ending, null);
_scheduler.Stop();
}
}
Test passes. Write another one:
测试通过。再写一个:
[Test]
public void ShouldNotStopDisplayingWarningsWhenTimeRemains()
{
Mock<IDisplay> display = new Mock<IDisplay>(MockBehavior.Strict);
Mock<IScheduler> scheduler = new Mock<IScheduler>(MockBehavior.Strict);
scheduler.Setup(s => s.Start());
Foo foo = new Foo("Bar", scheduler.Object, display.Object);
scheduler.Raise(s => s.Alarm += null, new SchedulerEventArgs(1));
}
Test failed. Ah, you need condition for remaining time:
测试失败。啊,你需要剩余时间的条件:
private void Scheduler_Alarm(object sender, SchedulerEventArgs e)
{
if (e.RemainingTime > 0)
return;
_display.Execute(_name, WarningState.Ending, null);
_scheduler.Stop();
}
You can continue writing tests for your class, which responsible for handling scheduler alerts and executing some warnings on display. When you finish, you can write implementation for your ISchedulerinterface. It does not matter how you will implement scheduling - via System.Windows.Forms.Timer or via System.ThreadingTimer, or some other way.
您可以继续为您的类编写测试,它负责处理调度程序警报并在显示上执行一些警告。完成后,您可以为IScheduler接口编写实现。您将如何实现调度无关紧要 - 通过 System.Windows.Forms.Timer 或通过 System.ThreadingTimer,或其他方式。
回答by Ufuk Hac?o?ullar?
Is it worth testing the method? (since it's private)
是否值得测试该方法?(因为它是私人的)
Your purpose is to decide whether your code works or not. Even it's a private method, it should generate an output that can be reached by the public interface. You should design your class in a way that the user can know if it's working or not.
您的目的是决定您的代码是否有效。即使它是一个私有方法,它也应该生成一个公共接口可以访问的输出。你应该以用户可以知道它是否工作的方式设计你的类。
Also when you are unit testing, the callback assigned to Elapsed event of the timer is reachable if you can mock the timer.
此外,当您进行单元测试时,如果您可以模拟计时器,则可以访问分配给计时器的 Elapsed 事件的回调。
How can I test it? (I know I can have my test class inheriting the class I want to test...)
我该如何测试?(我知道我可以让我的测试类继承我想要测试的类......)
You can use an adapter class here. First you have to define an abstraction since Timer class doesn't offer one.
您可以在此处使用适配器类。首先,您必须定义一个抽象,因为 Timer 类不提供。
public interface ITimer
{
void Start();
void Stop();
double Interval { get; set; }
event ElapsedEventHandler Elapsed;
//and other members you need
}
Then you can implement this interface in an adapter class, just inheriting from the Timer class.
然后你可以在一个适配器类中实现这个接口,只是从 Timer 类继承。
public class TimerAdaper : Timer, ITimer { }
You should inject your abstraction in the constructor(or as a property) so you can mock it in your tests.
您应该在构造函数(或作为属性)中注入您的抽象,以便您可以在测试中模拟它。
public class MyClass
{
private readonly ITimer _timer;
public MyClass(ITimer timer)
{
_timer = timer
}
}
Should I be mocking my timer? Because if I have to test a class that uses an internal timer, my tests may take a lot of time to complete, right?
我应该嘲笑我的计时器吗?因为如果我必须测试一个使用内部计时器的类,我的测试可能需要很多时间才能完成,对吗?
Of course you should be mocking your timer. Your unit tests cannot depend on system time. You should raise events by mocking and see how your code behaves.
当然,你应该嘲笑你的计时器。您的单元测试不能依赖于系统时间。您应该通过模拟来引发事件并查看您的代码的行为。

