2017-06-15 40 views
1

我嘗試寫一些單元測試下面的代碼單元測試C#雙鎖模式

private bool _initCalled = false; 
private void Initialize() 
{ 
    if (!_initCalled) 
    { 
     lock (this) 
     { 
      if (_initCalled) 
       return; 
      NoConfigInit(); 
      _initCalled = true; 
     } 
    } 
} 

什麼是測試該代碼打的代碼路徑if (_initCalled) return的最佳方式?

代表此代碼的替代方法也很受歡迎,但我很好奇如何測試這些模式的正確性。

+0

你可以使用'懶惰'(雖然你並不需要在你的情況下,'T'),然後刻意測試它,因爲它是一個框架類。我仍然有興趣看到實際上單元測試的結果,儘管如此, – Rawling

+1

'private int init = 0; ... if(Interlocked.CompareExchange(ref init,1,0)== 1)return; NoConfigInit();'。使用單元測試測試多線程代碼是否正確是愚蠢的錯誤;即使您創建了數百個線程,都盡最大努力以創造性的方式同時觸發該方法,但它只會在生產代碼中發生單個競爭條件,而不會在您的模擬中發生,從而導致失敗。你最終會得到緩慢而複雜的測試,但仍然會給你一點信心。最好的方法是隻使用明顯正確的代碼。 –

+0

在這種情況下,另一個(更清晰的)替代方法是簡單地鎖定(initMonitor){if(initDone)return; initDone = true; }; NoConfigInit();'。 (無論如何,[避免'lock(this)'](https://stackoverflow.com/questions/251391/why-is-lockthis-bad))。雙重檢查鎖定很花哨,但通常是不必要的。鎖的性能影響常常被嚴重高估 - 無爭議的鎖幾乎沒有。你每秒鐘調用'Initialize'幾千次嗎?可能不會。 –

回答

1

我有辦法來測試你寫的代碼,但它有幾個條件:

  1. 你需要使用Visual Studio來使用 微軟正版正貨的企業版(你可能能夠得到一個類似的概念工作 與免費替代品稱爲Prig,但我沒有經驗與它)

  2. 您必須針對.net框架,而不是.net核心。

我們必須改變你的代碼一點點,像這樣:

public class Class3 
    { 
    private bool _initCalled = false; 
    public void Initialize() 
    { 
     if (!_initCalled) 
     { 
     lock (this) 
     { 
      // we need to insert an empty DummyMethod here 
      DummyMethod(); 
      if (_initCalled) 
      return; 
      NoConfigInit(); 
      _initCalled = true; 
     } 
     } 
    } 

    private void DummyMethod() 
    { 
     // This method stays empty in production code. 
     // It provides the hook for the unit test. 
    } 

    private void NoConfigInit() 
    { 

    } 
} 

然後,生成假貨的庫之後,我們可以寫測試,如下所示:

[TestMethod] 
public void TestMethod1() 
{ 
    using (ShimsContext.Create()) 
    { 
    // This is the value to capture whether the NoConfigInit was called 
    var initCalled = false; 

    // Here the DummyMethod comes into play 
    Namespace.Fakes.ShimClass3.AllInstances.DummyMethod = 
     class3 => 
     typeof(Class3).GetField("_initCalled", BindingFlags.Instance | BindingFlags.NonPublic) 
      .SetValue(class3, true); 

    // This is just a hook to check whether the NoConfigInit is called. 
    // You may be able to test this using some other ways (e.g. asserting on state etc.) 
    Namespace.Fakes.ShimClass3.AllInstances.NoConfigInit = class3 => initCalled = true; 

    // act 
    new Class3().Initialize(); 

    // assert 
    Assert.IsFalse(initCalled); 
    } 
} 

如果您調試測試,您會看到它在第二次檢查時退出。

我同意這不是測試它的理想方法,因爲我們必須修改原始代碼。

沿着同樣的路線的另一種選擇是將_initCalled更改爲一個屬性 - >然後Fakes可以鉤入setter和getter,這樣你就可以避開DummyMethod,並且簡單地在第二次調用時返回true,就像這樣(在單元測試中):

 int calls = 0; 
    Namespace.Fakes.ShimClass3.AllInstances.InitCalledGet = class3 => calls++ > 0;