C#:如何测试基本的线程工作者类
我正在考虑如何测试multithreading的东西,但不太确定如何开始。 我相信如果我能把事情搞定,我会更容易找到更多的东西,所以我想知道是否有人可以帮助我为这个简单的类编写一个NUnit测试用例:
class Worker { public event EventHandler Done = (s, e) => { }; public void StartWork() { var thread = new Thread(Work) { Name = "Worker Thread" }; thread.Start(); } private void Work() { // Do some heavy lifting Thread.Sleep(500); Done(this, EventArgs.Empty); } }
我想测试的是: Done
事件在Done
时是否会被提升。 如果它是同步的话我会没有问题,但是当它不是时,我也不确定从哪里开始。 一个简单的测试,如果它不是multithreading的(并且Work
方法不是私有的)可能是:
[TestFixture] class WorkerTests { [Test] public void DoWork_WhenDone_EventIsRaised() { var worker = new Worker(); var eventWasRaised = false; worker.Done += (s, e) => eventWasRaised = true; worker.Work(); Assert.That(eventWasRaised); } }
有什么指针吗?
您需要使用ManualResetEvent – 有关详细信息,请参阅unit testingmultithreading异步事件 。
就像是:
[Test] public void DoWork_WhenDone_EventIsRaised() { var worker = new Worker(); var eventWasRaised = false; var mre = new ManualResetEvent(false); worker.Done += (s, e) => { eventWasRaised= true; mre.Set(); }; worker.Work(); mre.WaitOne(1000); Assert.That(eventWasRaised); }
您在测试线程应用程序时发现的主要问题实际上是通过测试数据来激活线程,因为您需要阻塞主线程以等待另一个线程退出。
我们使用它的方法是按照你的建议同步测试它。 这允许您测试逻辑行为,但它当然不会检测死锁和竞争条件(而不是测试可以轻松地断言这些事情)。
这里有两个选项:1)向worker添加一个Wait方法,以便你可以等待完成2)而不是简单的boolean use事件对象(AutoResetEvent)
通常,每次等待都必须等待指定的超时。 在下面的示例中,等待是无限的。
第一选择:
class Worker { //... Thread thread; public void StartWork() { thread = new Thread(Work) { Name = "Worker Thread" }; thread.Start(); } void WaitCompletion() { if ( thread != null ) thread.Join(); } //... } [TestFixture] class WorkerTests { [Test] public void DoWork_WhenDone_EventIsRaised() { var worker = new Worker(); var eventWasRaised = false; worker.Done += (s, e) => eventWasRaised = true; worker.Work(); worker.WaitCompletion(); Assert.That(eventWasRaised); } }
第二个选项:(等待可以超时完成)
[TestFixture] class WorkerTests { [Test] public void DoWork_WhenDone_EventIsRaised() { var worker = new Worker(); AutoResetEvent eventWasRaised = new AutoResetEvent(false); worker.Done += (s, e) => eventWasRaised.Set(); worker.Work(); Assert.That(eventWasRaised.WaitOne()); } }
您可以使用将线程创建公开给外部类的公共模式。
在类中将线程创建提取为虚拟方法:
class Worker { public event EventHandler Done = (s, e) => { }; public void StartWork() { var thread = CreateThread(); thread.Start(); } // Seam for extension and testability virtual protected Thread CreateThread() { return new Thread(Work) { Name = "Worker Thread" }; } private void Work() { // Do some heavy lifting Thread.Sleep(500); Done(this, EventArgs.Empty); } }
定义公开线程的子类:
class WorkerForTest : Worker { internal Thread thread; protected override Thread CreateThread() { thread = base.CreateThread(); return thread; } }
将测试与线程同步:
[TestFixture] class WorkerTests { [Test] public void DoWork_WhenDone_EventIsRaised() { var worker = new WorkerForTest(); var eventWasRaised = false; worker.Done += (s, e) => eventWasRaised = true; worker.StartWork(); // Use the seam for synchronizing the thread in the test worker.thread.Join(); Assert.That(eventWasRaised); } }
这种可测试性设计的案例优于通过在断言之前将其置于hibernate状态来同步测试线程:
- 它不会出现假负面故障,例如将测试线程置于hibernate期间,这通常足以让工作线程完成。
- 它不会运行得比它慢,因为睡眠时间需要缓冲来确保。 当套件中的许多测试依赖于它时,这很重要。