簡體   English   中英

是否應該對 Dispose 方法進行單元測試?

[英]Should Dispose methods be unit tested?

我正在使用 C#。 是否建議對處理方法進行單元測試? 如果是這樣,為什么,以及應該如何測試這些方法?

當然傷不起。 客戶端代碼可能會在處理掉類的對象后嘗試使用它。 如果您的類由其他IDisposable對象組成,並且它處於不再可用的狀態,則您應該始終拋出ObjectDisposedException異常。

當然,您應該只測試對象的外部狀態。 在下面的示例中,我將屬性Disposed外部以提供狀態。

考慮:

internal class CanBeDisposed : IDisposable
{
    private bool disposed;
    public bool Disposed
    {
        get
        {
            if (!this.disposed)
                return this.disposed;
            throw new ObjectDisposedException("CanBeDisposed");
        }
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                //// Dispose of managed resources.
            }
            //// Dispose of unmanaged resources.
            this.disposed = true;
        }
    }
}

所以我將如何測試它是這樣的:

CanBeDisposed cbd;

using (cbd = new CanBeDisposed())
{
    Debug.Assert(!cbd.Disposed); // Best not be disposed yet.
}

try
{
    Debug.Assert(cbd.Disposed); // Expecting an exception.
}
catch (Exception ex)
{
    Debug.Assert(ex is ObjectDisposedException); // Better be the right one.
}

是的,但可能很難。 Dispose實現中通常會發生兩件事:

非托管資源被釋放。

在這種情況下,很難驗證代碼是否調用了Marshal.Release 一個可能的解決方案是注入一個對象,該對象可以進行處理並在測試期間將模擬傳遞給它。 這種效果的東西:

interface ComObjectReleaser {
    public virtual Release (IntPtr obj) {
       Marshal.Release(obj);
    }
}

class ClassWithComObject : IDisposable {

    public ClassWithComObject (ComObjectReleaser releaser) {
       m_releaser = releaser;
    }

    // Create an int object
    ComObjectReleaser m_releaser;
    int obj = 1;
    IntPtr m_pointer = Marshal.GetIUnknownForObject(obj);

    public void Dispose() {
      m_releaser.Release(m_pointer);
    }
}

//Using MOQ - the best mocking framework :)))
class ClassWithComObjectTest {

    public DisposeShouldReleaseComObject() {
       var releaserMock = new Mock<ComObjectReleaser>();
       var target = new ClassWithComObject(releaserMock);
       target.Dispose();
       releaserMock.Verify(r=>r.Dispose());
    }
}

調用其他類的Dispose方法

對此的解決方案可能不像上面那樣簡單。 在大多數情況下,Dispose 的實現不是虛擬的,因此很難模擬它。

一種方法是將那些其他對象包裝在一個可模擬的包裝器中,類似於System.Web.Abstractions命名空間為HttpContext類所做的 - 即定義HttpContextBase類和所有虛擬方法,這些虛擬方法只是將方法調用委托給真正的HttpContext類。

有關如何執行此類操作的更多想法,請查看System.IO.Abstractions項目。

如果您的類創建並使用非托管資源,那么您絕對應該確保 Dispose 像您期望的那樣工作 - 盡管可以說它更像是一個集成測試,因為您將不得不跳過的箍類型通過。

如果您的類只創建/使用托管資源(即它們實現 IDisposable ),那么您真正需要確保的是在正確的時間調用這些資源上的 Dispose 方法 - 如果您使用某種形式的 DI,那么您可以注入一個模擬並斷言調用了 Dispose。

看看你的 dispose 方法的復雜性——如果它們只有幾行長,可能有 1 個條件,問問自己是否真的對它們進行單元測試有好處。

大是 - 如果您的情況需要您實現 Dispose 功能 - 您最好確保它按照您的想法進行!

例如,我們有協調數據庫任務的類(想想 SSIS 包,但有 SqlConnection 和 SqlCommand 和 SqlBulkCopy 等)。

如果我沒有正確地實現我的 Dispose,我可能會有一個未提交的 SqlTransaction 或懸空的 SqlConnection。 如果我連續運行這些數據庫任務的多個實例,這將是非常糟糕的。

作為一個實用技巧(因為是的,你應該測試Dispose() )我的經驗是有兩種方法可以做到這一點而不會太麻煩。

IDisposer

第一個遵循 Igor 接受的答案 - 注入IDisposer類的IDisposer ,以便您可以調用

public void Dispose()
{
    _disposer.Release(_disposable);
}

在哪里

public interface IDisposer
{
    void Release(IDisposable disposable);
}

然后你需要做的就是模擬IDisposer並斷言它被調用一次並且你是金子。

工廠

第二個,也是我個人最喜歡的,是有一個工廠來制造你需要測試處理的東西。 這僅在工廠生成可模擬類型(接口、抽象類)時才有效,但是,嘿,情況幾乎總是如此,尤其是對於要處理的內容。 出於測試目的,模擬工廠,但讓它生成您要測試處理的事物的模擬實現。 然后你可以直接在你的模擬上斷言對Dispose調用。 類似的東西

public interface IFooFactory
{
    IFoo Create(); // where IFoo : IDisposable
}

public class MockFoo : IFoo
{
    // ugly, use something like Moq instead of this class
    public int DisposalCount { get; privat set; }

    public void Dispose()
    {
        DisposalCount++;
    }
}

public class MockFooFactory
{
    public MockFoo LatestFoo { get; private set; }

    public IFoo Create()
    { 
        LatestFoo = new MockFoo();
        return LatestFoo;
    }
}

現在您可以隨時要求工廠(將在您的測試中可用)為您提供最新的MockFoo ,然后您處理外部事物並檢查DisposalCount == 1 (盡管您應該使用測試框架代替,例如 Moq) .

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM