2011-02-22 30 views
4

我在ASP.NET WebForms應用程序中使用Model-View-Presenter的樣板實現。我的視圖有兩個結果事件,一個表示用戶在域模型上填充了足夠的字段以啓動重複檢查,另一個是常規的保存事件。我的僞代碼如下所示:TDD可以強制創建「假」依賴關係

public class ItemNewPresenter : PresenterBase<IItemNewView> 
{ 
public IItemService Service { get; private set; } 
public IItemNewView View { get; private set; } 

public ItemNewPresenter(IItemService service, IItemNewView view) 
{ 
    Service = service; 
    View = view; 
    View.OnSave += DoItemSave; 
    View.OnItemIsDuplicateCheck+= DoItemIsDuplicateCheck; 
} 


private void DoItemIsDuplicateCheck(object sender, CheckItemDuplicateEventArgs e) 
{ 
    CheckForItemDuplication(e.Item); 
} 

private void CheckForItemDuplication(Item item){ 

if (Service.IsDuplicateItem(item)) 
    { 
     View.RedirectWithNotification(BuildItemUrl(item), "This item already exists"); 
    } 
} 
private void DoItemSave(object sender, SaveItemEventArgs e) 
{ 
    DoItemIsDuplicateCheck(this, e.ToItemDuplicateEventArgs()); 
    Service.Save(e.Item); 
} 

} 

這是我確保當OnItemIsDuplicateCheck從觀點提出了我演示正確的行爲測試:

[Test] 
public void presenter_checking_for_existing_item_should_call_redirect_if_found() 
{ 
    var service = new Mock<IItemService>(); 
    var view = new Mock<IItemNewView>(); 
    var presenter = new ItemNewPresenter (service.Object, view.Object); 

    var onCheckExistingHandler = view.CreateEventHandler <CheckItemDuplicateEventArgs>(); 
    view.Object.OnExistingDenominatorCheck += onCheckExistingHandler; 
    var eventArgs = new CheckItemDuplicateEventArgs(); 

    service.Setup(s => s.IsDuplicate(It.Is<CheckItemDuplicateEventArgs>(c => c.Equals(eventArgs)))).Returns(true); 

    onCheckExistingHandler.Raise(eventArgs); 

    view.Verify(v => v.RedirectWithNotification(It.IsAny<String>(), It.IsAny<string>()), Times.Once()); 
    service.Verify(); 
} 

爲了保持一致性,我想有同樣的重複當View引發OnSave事件時檢查被觸發。我的問題是,當我要驗證的方法之一(CheckForItemDuplication)在被測試的類上聲明時,我應該如何寫我的測試。驗證SUT方法調用(壞)的替代方法是用重複代碼編寫我的保存測試批次(我的所有嘲笑的設置和斷言都將從上述測試中複製),並且還會使單元測試不那麼專注。

[Test] 
    public void presenter_saving_item_should_check_for_dupe_and_save_if_not_one() { 
     //duplicate mocks/setups/asserts from duplicate check fixture 
     //additional mocks/setups/asserts to test save logic 
    } 

認爲 TDD建議拉這個私有方法到一個單獨的類,它與我的演講合作,並會通過DI注入。但是,向我的Presenter添加另一個依賴關係,看起來不值得作爲獨立抽象的功能*和*表示我的Presenter的內部實現細節,看起來......好吧......太瘋狂了。我在這裏基地?必須有一些設計模式或重構我可以應用,這將避免需要將私有方法變成依賴項。

回答

1

我會通過添加重複的設置代碼來測試類。一旦該測試通過並且您確信涵蓋了所有測試用例,您可以重構測試代碼以消除重複。

,您可以移動的依賴關係(服務和視圖),以私有字段,然後添加一個方法來創建SUT:

private Mock<IItemService> _service; 
private Mock<IItemNewView> _view; 

private PresenterBase<IItemNewView> CreateSUT() 
{ 
    _service = new Mock<IItemService>(); 
    _view = new Mock<IItemNewView>(); 
    return new ItemNewPresenter (service.Object, view.Object); 
} 

(我想大多數人寧願在設置方法來初始化Mock對象)

從您的測試中調用CreateSUT,現在重複數據少一些。然後,您可能需要添加私有方法來創建事件處理程序/引發事件,只要它是在多個測試用例中執行相同或相似的操作。

有了這個CreateSUT方法,可以減少調用構造函數的測試代碼的數量,以便將來添加/刪除/更改依賴關係。如果您將測試代碼像任何其他代碼一樣對待,並且在看到重複時使用DRY原則,則可以更清晰,更易於閱讀和維護測試代碼。處理非常相似的設置和測試環境是單元測試的常見問題,不應該總是改變被測試類的設計方式。

5

我覺得你陷入了TDD和信息隱藏之間永無止境的爭論中,因爲你接受注射可能是正確的事情(可能是),但也認爲外界的交流不應該在乎關於一個看似不重要的注射。

請不要向下投我爲我smellyness什麼,我要說的話:-)

現在,我有時會做,當這種尷尬局面的面對,是提取功能,使用該對象作爲參數創建一個內部構造函數,並且不帶一個公共構造函數。公共構造函數被轉發到內部用一個新的對象,如:

public class ClassThatUseInjection 
{ 
    private readonly SomeClass _injectedClass; 

    public ClassThatUseInjection(): this(new SomeClass()) {} 

    internal ClassThatUseInjection(SomeClass injectedClass) 
    { 
     _injectedClass = injectedClass; 
    } 
} 


public class SomeClass 
{ 
    public object SomeProperty { get; set; } 
} 

因此,你可以在需要時注入的testpurposes一個存根參數中使用來自外部的空的構造,以及其他的構造函數。只要空的構造函數只轉發沒有任何邏輯的調用,你仍然可以測試它,就像它只有一個構造函數一樣。

還是有點臭,是的,但不臭 - 臭:-)或者你怎麼看?

問候, 莫滕

+1

有趣的方法,但我覺得必須有更好的方法。正如你所指出的,我試圖測試的代碼是SUT的私有實現細節,並且永遠不需要通過消費類注入。引入一個新的類來封裝內部行爲只是爲了實現免費的測試代碼似乎徹頭徹尾的瘋狂:( – 2011-02-26 17:22:55

1

,如果有更好的答案,我會感興趣,因爲我遇到這一切的時候。

驗證SUT(壞)方法調用的替代方法是用大量重複代碼編寫我的保存測試(我的所有模擬的設置和斷言都將從上面的測試中複製)單元測試不那麼專注。

我不確定爲什麼你覺得它使得測試不那麼集中,但在你的鞋子裏,我會做它聽起來像你不想做的事 - 重複設置代碼來測試孤立的情況下SUT。你正在用你提供的測試來測試SUT的外部行爲,這對我來說似乎完全正確。

我個人並不是一個暴露超過從一個類和/或行爲應該是SUT的責任進入一個依賴只是爲了方便測試的必要。不應僅僅因爲你想測試它而違反班級責任的「自然邊界」。

1

單元測試url的計算比重定向發生的單元測試更容易。

如果我corretly瞭解你要測試的MVP-S CheckForItemDuplication()通過提高 視圖 - 模擬-S OnItemIsDuplicateCheck事件重定向到特定URL中。

private void CheckForItemDuplication(Item item) 
{ 
    if (Service.IsDuplicateItem(item)) 
    { 
     View.RedirectWithNotification(BuildItemUrl(item), 
         "This item already exists"); 
    } 
} 

在我看來,你做的很多。 如果你重寫代碼爲

internal protected GetErrorUrlForItem(Item item) 
{ 
    if (Service.IsDuplicateItem(item)) 
    { 
     return BuildItemUrl(item, 
          "This item already exists"); 
    } 
    return null; 
} 

private void CheckForItemDuplication(Item item) 
{ 
    var result = GetErrorUrlForItem(item); 
    if (result != null) 
    { 
     View.RedirectWithNotification(result); 
    } 
} 

在單元測試只是測試的內部方法GetErrorUrlForItem()。您必須使用InternalsVisibleTo屬性來允許訪問內部方法。

相關問題