2010-02-25 87 views
7

我有一個問題單元測試一個線程啓動並完成時觸發事件的類。違規源的削減版本如下:單元測試線程事件觸發

public class ThreadRunner 
{ 
    private bool keepRunning; 

    public event EventHandler Started; 
    public event EventHandler Finished; 

    public void StartThreadTest() 
    { 
     this.keepRunning = true; 
     var thread = new Thread(new ThreadStart(this.LongRunningMethod)); 
     thread.Start(); 
    } 

    public void FinishThreadTest() 
    { 
     this.keepRunning = false; 
    } 

    protected void OnStarted() 
    { 
     if (this.Started != null) 
      this.Started(this, new EventArgs()); 
    } 

    protected void OnFinished() 
    { 
     if (this.Finished != null) 
      this.Finished(this, new EventArgs()); 
    } 

    private void LongRunningMethod() 
    { 
     this.OnStarted(); 

     while (this.keepRunning) 
      Thread.Sleep(100); 

     this.OnFinished(); 
    } 
} 

然後我有一個測試,以檢查LongRunningMethodFinished事件觸發完成如下:

[TestClass] 
public class ThreadRunnerTests 
{ 
    [TestMethod] 
    public void CheckFinishedEventFiresTest() 
    { 
     var threadTest = new ThreadRunner(); 

     bool finished = false; 

     object locker = new object(); 

     threadTest.Finished += delegate(object sender, EventArgs e) 
     { 
      lock (locker) 
      { 
       finished = true; 
       Monitor.Pulse(locker); 
      } 
     }; 

     threadTest.StartThreadTest(); 
     threadTest.FinishThreadTest(); 

     lock (locker) 
     { 
      Monitor.Wait(locker, 1000); 
      Assert.IsTrue(finished); 
     } 
    } 
} 

這樣的想法在這裏因爲測試會阻止最多1秒 - 或者直到Finish事件被觸發 - 在檢查finished標誌是否被設置之前。

很明顯,我做了一些錯誤的事,因爲有時測試會通過,有時不會。調試似乎非常困難,以及我預計會遇到的斷點(例如OnFinished方法)似乎並不總是如此。

我假設這只是我對線程工作方式的誤解,所以希望有人能夠啓發我。

+0

感謝所有的答覆。我還通過使線程工作者方法(即LongRunningMethod)在啓動時將其設置爲自己的控制標記來引入另一個錯誤。今天肯定是如何在你的代碼中引入競爭條件的速成班,doh! – Dougc

回答

15

鎖是不適合這裏,你會希望信號的事件。例如:

public void CheckFinishedEventFiresTest() { 
     var threadTest = new ThreadRunner(); 
     var finished = new ManualResetEvent(false); 
     threadTest.Finished += delegate(object sender, EventArgs e) { 
      finished.Set(); 
     }; 
     threadTest.StartThreadTest(); 
     threadTest.FinishThreadTest(); 
     Assert.IsTrue(finished.WaitOne(1000)); 
    } 
2

您測試似乎是錯誤的。假設threadTest.FinishThreadTest();鎖是通過CheckFinishedEventFiresTest()中的代碼獲得的。然後測試將失敗。你在這裏有一個明確的競爭條件。

請注意,從FinishThreadTest()返回的不是保證線程已完成。它只是爲線程設置標誌,這可能會在任何時候考慮到(基本上不能保證線程立即由調度程序運行)。

在你的情況下,線程很可能會很忙Sleep() ing。在調用threadTest.FinishThreadTest();之後,該鎖將最有可能通過執行CheckFinishedEventFiresTest()的線程獲得。顯示器將等待1秒鐘,然後放棄。之後,鎖將被釋放,所以委託人將只能在此時鎖定。

+0

啊,現在肯定有道理。謝謝弗拉德。 – Dougc

4

弗拉德是絕對正確的,但我會再出手,以澄清問題:

// This runs on the other thread 
threadTest.Finished += delegate(object sender, EventArgs e) { 
    // I can't get this lock if the test thread gets here first! 
    lock (locker) { 
     finished = true; 
     Monitor.Pulse(locker); 
    } 
}; 

您可以用某種類型的等待句柄做到這一點。我會使用一個ManualResetEvent

ManualResetEvent waitHandle = new ManualResetEvent(false); 
threadTest.Finished += delegate(object sender, EventArgs e) { 
    finished = true; 
    waitHandle.Set(); 
}; 

threadTest.StartThreadTest(); 
threadTest.FinishThreadTest(); 

// Specify a timeout so your test isn't hostage forever 
if (waitHandle.WaitOne(timeout, true)) { 
    Assert.IsTrue(finished); 
} 
+0

謝謝傑夫。似乎還有一個奇怪的問題,在FinishRunningTest方法切換keepRunning標誌後,線程有時不會退出?即使我把超時時間設置得很高。 – Dougc

+0

'LongRunningMethod'不退出?也許第二個WaitOne參數有一些有趣的事情(退出上下文)。我會嘗試nobugz'樣本,其目的是完成同樣的事情,但無論如何(取消多餘的布爾)更好。 –

+0

沒關係,我是一個白癡(請參閱我對原始問題的評論)。謝謝! – Dougc

3

我最近寫爲發佈同步和異步事件的對象上進行單元測試事件序列的一系列博客文章。這些文章描述了單元測試方法和框架,並提供了完整的源代碼和測試。

使用框架的測試可以寫成像這樣:

AsyncEventPublisher publisher = new AsyncEventPublisher(); 

Action test =() => 
{ 
    publisher.RaiseA(); 
    publisher.RaiseB(); 
    publisher.RaiseC(); 
}; 

var expectedSequence = new[] { "EventA", "EventB", "EventC" }; 

EventMonitor.Assert(test, publisher, expectedSequence, TimeoutMS); 

的EventMonitor做所有繁重的任務,並運行測試(行動),並聲稱事件的預期序列(expectedSequence)中提出。它處理異步事件,並在測試失敗時打印出良好的診斷消息。

有一個在描述問題和方法,和源代碼太帖子很多細節:

http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/

相關問題