2009-11-20 56 views
5

我正在如何測試多線程的東西,但我不知道如何開始。我敢肯定,我將人物更多的東西出來容易,如果我能得到的東西去,所以我想知道,如果有人可以幫我寫一個NUnit測試案例這個簡單的類:C#:如何測試一個基本的線程工人類

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

任何指針?

回答

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); 
} 
+2

eventRaisedFlag可以省略。使用事件作爲標誌更簡單。 Assert.That(mre.WaitOne(1000)); – 2009-11-20 14:50:16

+0

哦,現在很聰明...輝煌! @Vadmyst:如果說事件在您調用WaitOne之前設法完成,那麼這將如何工作? – Svish 2009-11-20 16:28:56

+0

哦,我想這就是爲什麼你會使用ManualResetEvent而不是AutoResetEvent? – Svish 2009-11-26 18:47:43

1

主要的問題你測試線程應用程序發現實際上是刺激與測試數據的線程,因爲你需要阻止主線程等待,直到其他線程退出。

我們與之合作的方式是按照您的建議同步測試它。這允許您測試邏輯行爲,但它當然不會檢測到死鎖和競爭條件(不是說測試可以輕易地斷言這些事情)。

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

這種可測試性的設計案例比通過puttin同步測試線程更有優勢摹它斷言睡覺前:

  • 它不會有假陰性的失敗把測試線程睡眠的時間,一般是足夠的工作線程結束時象可能。
  • 它不會運行得慢,因爲睡眠時間需要緩衝區來確保。當套件中的許多測試依賴於此時,這是非常重要的。
1

可以有兩個選擇: 1)添加一個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()); 
    } 
}