![](/img/trans.png)
[英]Should protected methods be unit-tested? How to avoid repetitive testing?
[英]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()
)我的經驗是有兩種方法可以做到這一點而不會太麻煩。
第一個遵循 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.