2009-02-25 228 views
12

我真的很陌生,我試圖用模擬對象替換私人領域。目前私有字段的實例是在構造函數中創建的。我的代碼看起來像......如何模擬私人領域?

public class Cache { 
    private ISnapshot _lastest_snapshot; 

    public ISnapshot LatestSnapshot { 
     get { return this._lastest_snapshot; } 
     private set { this._latest_snapshot = value; } 
    } 

    public Cache() { 
     this.LatestSnapshot = new Snapshot(); 
    } 

    public void Freeze(IUpdates Updates) { 
     ISnapshot _next = this.LastestSnapshot.CreateNext(); 
     _next.FreezeFrom(Updates); 
     this.LastestSnapshot = _next; 
    } 

} 

我想要做的就是創建一個單元測試,斷言ISnapshot.FreezeFrom(IUpdates)Cache.Freeze(IUpdates)中調用。我猜我應該用模擬對象替換私人領域_latest_snapshot(也許錯誤的假設?)。我會怎樣去解決這個問題,同時仍然保留一個無參數的構造函數,而不是使LatestSnapshot被公開?

如果我完全想用錯誤的方式編寫測試,那麼請指出。

ISnapshot.FreezeFrom本身的實際實現本身調用了其他方法的深層對象圖,所以我並不太熱衷於斷言對象圖。

在此先感謝。

回答

16

我幾乎是從"Working Effectively with Legacy Code"援引技術:

  1. 子類在單元測試類,並在它模擬對象取代您的私有變量(通過增加公共setter方法或構造函數) 。您可能必須使變量受保護。
  2. 爲此私有變量創建一個受保護的getter,並在測試子類中覆蓋它以返回一個模擬對象而不是實際的私有變量。
  3. 創建受保護的工廠方法,用於創建ISnapshot對象,並在測試子類中覆蓋它以返回模擬對象的實例,而不是真實的對象。這樣構造函數將從一開始就獲得正確的值。
  4. 參數化構造函數以ISnapshot爲例。
4

我不認爲你需要模擬私有成員變量。嘲笑一個對象的公共接口是否按預期工作的整個想法?私有變量是嘲笑不關心的實現細節。

+4

如果被測試的類使用表示數據庫的私有字段會怎麼樣?我如何模擬該數據庫字段? – Vanuan 2011-05-11 19:50:15

+0

你想知道存儲庫模式,它基本上隱藏了一組接口後面的數據存儲,所以它們可以很容易地進行模擬和測試。 – Jason 2011-05-12 14:53:51

+1

但是,你是如何構建模擬存儲庫並將其傳遞給消費者的?你需要一個setter還是一個構造函數參數? – Vanuan 2011-05-12 18:29:24

3

我不確定你能做到這一點。如果你想測試_next,那麼你可能需要將它作爲參數傳入,然後在單元測試中傳遞一個Mock對象,然後使用Expectation進行測試。如果我在Moq中嘗試這樣做,那就是我所要做的。

至於什麼,我可能會嘗試使用起訂量框架的例子:

Mock<ISnapshot> snapshotMock = new Mock<ISnapshot>(); 
snapshotMock.Expect(p => p.FreezeFrom(expectedUpdate)).AtMostOnce(); 
Cache c = new Cache(snapshotMock.Object); 
c.Freeze(expectedUpdate); 

注:我還沒有嘗試編譯上面的代碼。它只是爲了舉例說明我如何解決這個問題。

1

這個答案可能很簡單,但看着代碼,有沒有什麼辦法可以讓ISnapshot.FreezeFrom(IUpdates)不會被調用?聽起來像你想斷言永遠是真的東西。

賈森說,嘲諷意味着對於有些情況下你的等級取決於SomeInterface做的工作,要在從哪個實施SomeInterface你實際上是在運行時使用隔離測試YourClass

1

問的問題是:如果這種方法奏效,外部可見效應是什麼?

所有這些快照會發生什麼?一種選擇可能是從外部初始化緩存並使用它的第一個快照,比如在構造函數中。另一種可能是嘲笑快照在緩存之外的調用。這取決於你在乎的是什麼。

1

迴應可能爲時已晚。無論如何。我也有類似的問題。

public class Model 
{ 
    public ISomeClass XYZ{ 
     get; 
     private set; 
     } 
} 

我需要在我的測試用例中設置XYZ的值。我用這個syntex解決了這個問題。

Expect.Call(_model.XYZ).Return(new SomeClass()); 
_repository.ReplayAll(); 

在上述情況下,我們可以做這樣的

Expect.Call(_cache.LatestSnapshot).Return(new Snapshot()); 
_repository.ReplayAll(); 
-2

打開緩存到模板中,如下圖所示。

template <typename T=ISnapshot> 
public class Cache { 
    private T _lastest_snapshot; 

    public T LatestSnapshot { 
     get { return this._lastest_snapshot; } 
     private set { this._latest_snapshot = value; } 
    } 

    public Cache() { 
     this.LatestSnapshot = new Snapshot(); 
    } 

    public void Freeze(IUpdates Updates) { 
     T _next = this.LastestSnapshot.CreateNext(); 
     _next.FreezeFrom(Updates); 
     this.LastestSnapshot = _next; 
    } 

} 

在生產代碼做:

Cache<> foo;//OR 
Cache<ISnapshot> bar; 

在測試代碼的作用:

Cache<MockSnapshot> mockFoo; 
0

你可能將不得不重構你的類像這樣,爲了使它與被注入ISnapshot的不同依賴關係。你的班級將繼續運作相同。

public class Cache { 
private ISnapshot _lastest_snapshot; 

public ISnapshot LatestSnapshot { 
    get { return this._lastest_snapshot; } 
    private set { this._latest_snapshot = value; } 
} 

public Cache() : this (new Snapshot()) { 
} 

public Cache(ISnapshot latestSnapshot) { 
    this.LatestSnapshot = latestSnapshot; 
} 

public void Freeze(IUpdates Updates) { 
    ISnapshot _next = this.LastestSnapshot.CreateNext(); 
    _next.FreezeFrom(Updates); 
    this.LastestSnapshot = _next; 
} 

} 
0

您可以使用模擬類實例簡單地將「setSnapshot(ISnapshot)」方法添加到緩存中。

您還可以添加一個構造函數,它接受ISnapshot。