2013-04-17 151 views
2

此之前已經討論了很多次,但在下面的示例中的優點並不明顯,所以請多多包涵。C#單元測試 - 嘲笑,存根或使用顯式實現

我想決定是否在我的單元測試中使用模擬實現,並未定,給出以下兩個示例,第一個使用NSubstitute進行嘲諷,第二個使用SimpleInjector(Bootstrapper對象)解析實現。

本質上都被檢測爲同一件事,即設置構件設置爲true時.Dispose()方法被調用(請參閱本崗位的底部實現方法)。

爲了我的眼睛,該第二方法使得對迴歸測試更有意義的模擬代理顯式地設定設置構件在第一示例中是真實的,而它是由實際.Dispose()方法在注射的執行設定。

爲什麼你會建議我選擇了另一種用於驗證方法的行爲如預期?即調用.Dispose()方法,並通過此方法正確設置Disposed成員。

[Test] 
    public void Mock_socket_base_dispose_call_is_received() 
    { 
     var socketBase = Substitute.For<ISocketBase>(); 
     socketBase.Disposed.Should().BeFalse("this is the default disposed state."); 

     socketBase.Dispose(); 
     socketBase.Received(1).Dispose(); 

     socketBase.Disposed.Returns(true); 
     socketBase.Disposed.Should().BeTrue("the ISafeDisposable interface requires this."); 
    } 

    [Test] 
    public void Socket_base_is_marked_as_disposed() 
    { 
     var socketBase = Bootstrapper.GetInstance<ISocketBase>(); 
     socketBase.Disposed.Should().BeFalse("this is the default disposed state."); 
     socketBase.Dispose(); 
     socketBase.Disposed.Should().BeTrue("the ISafeDisposable interface requires this."); 
    } 

,以供參考.Dispose()方法很簡單:

/// <summary> 
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 
    /// </summary> 
    public void Dispose() 
    { 
     Dispose(true); 
     GC.SuppressFinalize(this); 
    } 

    /// <summary> 
    /// Releases unmanaged and - optionally - managed resources. 
    /// </summary> 
    /// <param name="disposeAndFinalize"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> 
    protected void Dispose(bool disposeAndFinalize) 
    { 
     if (Disposed) 
     { 
      return; 
     } 

     if (disposeAndFinalize) 
     { 
      DisposeManagedResources(); 
     } 

     DisposeUnmanagedResources(); 

     Disposed = true; 
    } 

乾杯

+0

我說保持你的測試儘可能簡單。如果測試足夠複雜,需要它,恕我直言,模擬。如果您使用VS2012,請嘗試利用[fakes](http://msdn.microsoft.com/zh-cn/library/hh549175.aspx)。在你的情況下,我沒有看到你的第二種方法有什麼問題(我沒有嘗試過你的代碼,但看起來不錯) –

+1

@GabeThorns:但是如果測試「夠複雜」,可能已經有問題了測試(或測試中的類別):-) – Steven

+1

我同意@Steven測試不應該是「複雜」的,因爲這往往表示代碼味道 – McDonnellDean

回答

2

兩種測試方法顯得相當怪異給我。與第一種方法,你似乎沒有測試任何東西(或者我可能會被誤解NSubstitute做什麼),因爲你剛纔嘲笑ISocketBase接口(即無行爲測試),並開始測試,而不是真正的實現,模擬對象。

第二種方法也不好,因爲您應該使用而不是在單元測試中使用任何DI容器。這隻會讓事情變得更加複雜,因爲:

  1. 您現在使用所有測試使用的共享狀態,這會使所有測試相互依賴(測試應該獨立運行)。
  2. 容器引導邏輯將變得非常複雜,因爲要插入的不同測試不同的模擬考試,並再次,沒有對象測試之間共享。
  3. 您的測試對框架或外觀有額外的依賴性,而這些依賴關係還不存在。從這個意義上說,你只是讓你的測試更加複雜。它可能只是更復雜一點,但它是一個額外的複雜因素。

相反,你應該做的是總是創建單元測試(或測試工廠方法)本身的內部測試(SUT)的類。您可能仍然想使用模擬框架創建SUTs依賴項,但這是可選的。因此,IMO測試應該看起來像這樣:

[Test] 
public void A_nondisposed_Socket_base_should_not_be_marked_dispose() 
{ 
    // Arrange 
    Socket socket = CreateValidSocket(); 

    // Assert 
    socketBase.Disposed.Should().BeFalse(
     "A non-disposed socket should not be flagged."); 
} 

[Test] 
public void Socket_base_is_marked_as_disposed_after_calling_dispose() 
{ 
    // Arrange 
    Socket socket = CreateValidSocket(); 

    // Act 
    socketBase.Dispose(); 

    // Assert 
    socketBase.Disposed.Should().BeTrue(
     "Should be flagged as Disposed."); 
} 

private static Socket CreateValidSocket() 
{ 
    return new Socket(
     new FakeDependency1(), new FakeDependency2()); 
} 

請注意,我將您的單個測試分成2個測試。在調用dispose之前,Disposed應該是錯誤的,並不是測試運行的先決條件;這是系統工作的要求。換句話說,你需要明確這一點,並需要第二次測試。

另請注意,使用CreateValidSocket工廠方法可重複使用多個測試。當其他測試檢查需要更多特定僞造或模擬對象的類的其他部分時,您可能對此方法有多個重載(或可選參數)。

+0

NS替代品(無論如何,它應該被分成兩個測試)僅測試一次調用.Dispose()方法,並將Disposed成員設置爲true。我不覺得這個特別有用,因爲如果我明確地將它設置爲我自己,那麼Disposed將等於true? – Jimmy

+0

我不明白你的評論。當你需要測試的結果是什麼時,你爲什麼要將'Disposed'設置爲true?你想測試的是「當Dispose()不被調用時Displace爲false」和「Dispose IS被調用時Displace爲true」。 – Steven

+0

您的NOT和ALWAYS斷言很簡單。您還斷言DI容器不僅僅是一種請求依賴性的方式,當您想要使用DI容器時,不需要使用DI框架。 – McDonnellDean

1

您關心太多。這個測試是測試天氣或不是一個給定的實現正確處置,因此你的測試應該反映這一點。請參閱下面的僞代碼。非脆性測試的訣竅是僅測試滿足測試所需的絕對最小值。

public class When_disposed_is_called() 
{ 
    public void The_object_should_be_disposed() 
    { 
     var disposableObjects = someContainer.GetAll<IDisposable>(); 
     disposableObjects.ForEach(obj => obj.Dispose()); 
     Assert.False(disposableObject.Any(obj => obj.IsDisposed == false)); 
    } 
} 

正如你可以看到我填補一些依賴容器都在我的關切,實現IDisposable的對象。我可能不得不嘲笑他們或做其他事情,但那不是測試的關注。最終,它只關心驗證什麼時候處置什麼事實上應該被處置。