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

C#:如何测试基本线程工作类,c#,multithreading,events,nunit,C#,Multithreading,Events,Nunit,我正在研究如何测试多线程的东西,但不太确定如何开始。我相信,如果我能让这些东西运行起来,我会更容易地找到更多的东西,所以我想知道是否有人能帮我为这个简单的类编写一个NUnit测试用例: class Worker { public event EventHandler<EventArgs> Done = (s, e) => { }; public void StartWork() { var thread = new Thread(Wor

我正在研究如何测试多线程的东西,但不太确定如何开始。我相信,如果我能让这些东西运行起来,我会更容易地找到更多的东西,所以我想知道是否有人能帮我为这个简单的类编写一个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);
    }
}

有任何指针吗?

您需要使用手动重置事件-有关详细信息,请参阅

比如:

[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);
}

测试线程化应用程序时发现的主要问题实际上是使用测试数据刺激线程,因为您需要在主线程上阻塞,以等待其他线程退出


我们处理这个问题的方法是按照您的建议同步测试它。这允许您测试逻辑行为,但它当然不会检测死锁和争用条件(无论如何,测试也不能轻易断言这些情况)。

您可以使用一个公共模式,将线程创建公开给外部类

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

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);
    }
}
与同步测试线程相比,这种可测试性设计具有以下优点:在断言之前将测试线程置于休眠状态:

  • 它不会像将测试线程休眠一段时间(通常足以让工作线程完成)那样出现假阴性故障
  • 它不会运行得比必须的慢,因为睡眠时间需要缓冲区来确保。当套件中的许多测试依赖于它时,这一点很重要

这里有两个选项: 1) 将Wait方法添加到worker,以便可以等待完成 2) 使用事件对象(AutoResteEvent)代替简单的布尔值

通常,每次等待都必须等待指定的超时。在下面的示例中,等待是无限的

第一种选择:

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);
    }
}
第二个选项:(可以通过超时完成等待)


eventRaisedFlag可以省略。使用事件作为标志更简单。断言(WaitOne先生(1000));哦,这太聪明了。。。太棒了@Vadmyst:如果说事件在你呼叫WaitOne之前完成了,那该怎么办?哦,我想这就是为什么你会使用手动重置事件而不是自动重置事件?
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());
    }
}