2009-02-18 42 views
4

我有一個包含這樣的異步調用的方法:如何對包含異步調用的方法進行單元測試?

public void MyMethod() { 
    ... 
    (new Action<string>(worker.DoWork)).BeginInvoke(myString, null, null); 
    ... 
} 

我使用的是統一和創建模擬對象是沒有問題的,但我怎麼能測試的DoWork稱爲無需擔心競爭條件?

A previous question提供了一個解決方案,但在我看來,等待句柄是一種破解(競爭條件仍然存在,儘管實際上不可能提高)。


編輯:好的,我想我會問這個問題的一個非常普遍的方式,但似乎我不得不進一步闡述這個問題:

我想創建上述測試的MyMethod,所以我做這樣的事情:

[TestMethod] 
public void TestMyMethod() { 
    ...setup... 
    MockWorker worker = new MockWorker(); 
    MyObject myObj = new MyObject(worker); 
    ...assert preconditions... 
    myObj.MyMethod(); 
    ...assert postconditions... 
} 

天真的方法是創建一個MockWorker(),簡單地設置一個當DoWork的被稱爲標誌,並測試該標誌在後置條件。這當然會導致競爭條件,在MockWorker中設置標誌之前檢查後置條件。

更正確的做法(這我可能會最終使用)使用等待句柄:

...並使用以下斷言:

Assert.IsTrue(worker.AutoResetEvent.WaitOne(1000, false)); 

這是這很好......但在理論下面可能會發生:

  1. 在我的DoWork代理上調用BeginInvoke
  2. 由於某些原因,主線程或DoWork線程的執行時間均爲1000毫秒。
  3. 主線程被賦予執行時間,並且由於超時斷言失敗,即使DoWork線程尚未執行。

我誤解了AutoResetEvent的工作原理嗎?我只是太偏執,還是有一個聰明的解決方案來解決這個問題?

+0

你能解釋一下這種情況下的比賽狀況嗎?這幾乎是我使用的解決方案,它永遠不會讓我失望。 – 2009-02-18 15:05:10

+0

不,我相信它永遠不會失敗,因爲競爭條件主要是理論上的。 競爭條件很簡單:如果由於某種原因,工作線程在超時之前從未被賦予執行時間,則測試錯誤地失敗。 我知道!這是猜測性的,但我知道墨菲定律;-) – toxvaerd 2009-02-18 15:26:10

+0

我見過使用這些標誌的測試會在重負載的構建服務器上失敗(需要重建以增加負載)。走信號路線並使用暫停對我來說似乎更好。另外,使用標誌意味着你必須在整個一段時間內進入睡眠狀態,這可能會讓你的測試套件花費很多時間才能運行。 – 2009-05-09 19:32:04

回答

3

等待處理將是我會這樣做的方式。 AFAIK(我不知道肯定)異步方法正在使用等待句柄來觸發該方法。

我不確定爲什麼你會認爲競爭情況會發生,除非你在WaitOne調用中給出異常短的時間。我會等待4-5秒,這樣你肯定知道它是否被破壞,而不僅僅是一場比賽。

另外,不要忘記如何等待句柄的工作,只要在創建等待句柄,您可以執行下列順序

  • 線程1 - 創建等待處理
  • 線程1 - 設置等候處理
  • 線程2 - 等待句柄的WaitOne
  • 線程2 - 通過等待句柄打擊,並繼續執行

即使正常執行是

  • 線程1 - 創建等待處理
  • 線程2 - 的WaitOne在等待句柄
  • 線程1 - 集等待處理
  • 線程2 - 通過等待句柄打擊並繼續執行

可以正常工作,可以在Thread2開始等待它之前設置等待句柄,並且爲您處理所有事情。

+0

你能詳細解釋一下嗎?我知道比賽條件是非常理論的,但它似乎是我們出貨的那一天會出錯的;-) – toxvaerd 2009-02-18 15:28:34

3

當測試直接委託時,只需使用EndInvoke調用來確保委託被調用並返回相應的值。例如。

var del = new Action<string>(worker.DoWork); 
var async = del.BeginInvoke(myString,null,null); 
... 
var result = del.EndInvoke(async); 

EDIT

OP評論說,他們正試圖單元測試的MyMethod相對於worker.DoWork方法。

在這種情況下,您將不得不依靠調用DoWork方法的可見副作用。根據你的例子,我不能在這裏提供太多內容,因爲DoWork的內部工作都沒有公開。

EDIT2

[OP]出於某種原因,既不是主線程或DoWork的線程,給出了1000毫秒執行時間。

這不會發生。當您在AutoResetEvent上調用WaitOne時,線程會進入休眠狀態。直到事件設置或超時時間到期,它纔會收到處理器時間。一些其他線程獲得大量時間片並導致錯誤失敗是絕對可以的。但我認爲這是不太可能的。我有幾個以相同方式運行的測試,並且我沒有很多/任何這樣的錯誤失敗。我通常選擇一個超時〜2分鐘。

2

這就是我在這些情況下所做的:創建一個接受委託並執行它的服務(通過一個簡單的接口公開這個事件,注入你異步調用東西的地方)。

在真正的服務中,它將使用BeginInvoke執行,因此是異步執行的。然後創建一個測試服務版本,以同步方式調用委託。

下面是一個例子:

public interface IActionRunner 
{ 
    void Run(Action action, AsyncCallback callback, object obj); 
    void Run<T>(Action<T> action, T arg, AsyncCallback callback, object obj); 
    void Run<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2, AsyncCallback callback, object obj); 
    void Run<T1, T2, T3>(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3, AsyncCallback callback, object obj); 
    void Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4, AsyncCallback callback, object obj); 
} 

的Asycnhronous實現這項服務的是這樣的:

public void Run<T>(Action<T> action, T arg, AsyncCallback callback, object obj) 
{ 
    action.BeginInvoke(arg, callback, obj); 
} 

一個同步實現這項服務的是這樣的:

public void Run<T>(Action<T> action, T arg, AsyncCallback callback, object obj) 
{ 
    action(arg); 
} 

當你是單元測試,如果你正在使用RhinoMocks和AutoMoc之類的東西你可以在你的Synchronous ActionRunner中進行交換:

_mocks = new MockRepository(); 

_container = new AutoMockingContainer(_mocks); 
_container.AddService(typeof(IActionRunner), new SyncActionRunner()); 
_container.Initialize(); 

ActionRunner不需要測試;它只是方法調用的薄木板。

相關問題