C#:如何测试基本的线程工作者类

时间:2009-11-20 14:37:04

标签: c# multithreading events nunit

我正在考虑如何测试多线程的东西,但不太确定如何开始。我相信如果我能把事情搞定,我会更容易找到更多的东西,所以我想知道是否有人可以帮助我为这个简单的类写一个NUnit测试用例:

class Worker
{
    public event EventHandler<EventArgs> 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事件在完成时是否会被提升。如果它是同步的话我会没有问题,但是当它不是时,我也不确定从哪里开始。一个简单的测试,如果它不是多线程的(并且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);
    }
}

任何指针?

4 个答案:

答案 0 :(得分:7)

您需要使用ManualResetEvent - 有关详细信息,请参阅Unit Testing Multi-Threaded Asynchronous Events

类似的东西:

[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 :(得分:1)

测试线程应用程序时遇到的主要问题实际上是用测试数据来激活线程,因为你需要阻塞主线程才能等到另一个线程退出。

我们使用它的方法是按照您的建议同步测试它。这允许您测试逻辑行为,但它当然不会检测死锁和竞争条件(而不是测试可以轻松地断言这些事情)。

答案 2 :(得分:1)

您可以使用将线程创建公开给外部类的公共模式。

在类中将线程创建提取为虚拟方法:

class Worker
{
    public event EventHandler<EventArgs> 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);
    }
}

这种可测试性设计的案例优于通过在断言之前将其置于休眠状态来同步测试线程:

  • 它不会出现假负面故障,例如将测试线程置于休眠期间,这通常足以让工作线程完成。
  • 它的运行速度不会比它慢,因为睡眠时间需要缓冲来确保。当套件中的许多测试依赖于它时,这很重要。

答案 3 :(得分:1)

这里有两种选择: 1)向worker添加Wait方法,以便等待完成 2)而不是简单的布尔使用事件对象(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());
    }
}