簡體   English   中英

實例化其他類的單元測試類

[英]Unit testing classes that instantiate other classes

我正在嘗試為在其中實例化其他類的類編寫單元測試,但正在努力解決如何以可測試的方式實例化這些類。 我知道依賴注入,但這有點不同,因為實例化不會在構造函數中發生。

這個問題確實不是 MVVM 和 C# 特有的,但這就是我的示例將使用的。 請注意,我已經簡化了這一點,它不會按原樣編譯 - 目標是顯示一個模式。

class ItemListViewModel
{
  ItemListViewModel(IService service)
  {
    this.service.ItemAdded += this.OnItemAdded;
  }

  List<IItemViewModel> Items { get; }

  OnItemAdded(IItemModel addedItem)
  {
    var viewModel = new ItemViewModel(addedItem);
    this.Items.Add(viewModel);
  }
}

class ItemViewModel : IItemViewModel
{
  ItemViewModel(IItem) {}
}

從上面可以看出,模型層有一個事件。 ViewModel 偵聽該事件,並作為響應添加一個新的子 ViewModel。 這符合我所知道的標准面向對象編程實踐以及 MVVM 模式,對我來說感覺是一個非常干凈的實現。

當我想對這個 ViewModel 進行單元測試時,問題就出現了。 雖然我可以使用依賴注入輕松模擬服務,但我無法模擬通過事件添加的項目。 這引出了我的主要問題:是否可以根據 ItemViewModel 的真實版本而不是模擬來編寫我的單元測試?

我的直覺:這不行,因為我現在測試的不僅僅是 ItemListViewModel,特別是如果 ItemListViewModel 在內部調用任何項目的任何方法。 在測試期間,我應該讓 ItemListViewModel 依賴於模擬 IItemViewModels。

我已經考慮了一些如何做到這一點的策略:

  1. 讓 ItemListViewModel 的擁有類監聽事件並添加模擬項目。 不過,這只是解決了問題,因為現在無法完全模擬擁有類。
  2. 將 ItemViewModel 工廠傳遞到 ItemListViewModel 並使用它而不是新的。 這肯定適用於模擬,因為它使事情基於依賴注入......但這是否意味着我需要為我想要在我的應用程序中模擬的每個類都有一個工廠? 這感覺不對,維護起來會很痛苦。
  3. 重構我的模型以及它如何與 ViewModel 通信。 也許我使用的事件模式不適合測試; 雖然我不知道我將如何解決最終需要在需要測試的代碼中的某處構建一個 ItemViewModel 的問題。

此外,我在網上搜索並瀏覽了 Clean Code 一書,但這確實沒有被涵蓋。 一切都在談論依賴注入,這並沒有明確解決這個問題。

是否可以根據 ItemViewModel 的真實版本而不是模擬來編寫我的單元測試?

是的!
只要測試變得緩慢或設置起來非常復雜,您就應該使用真正的實現。

請注意,測試應該是隔離的,但要與其他測試隔離,而不是與被測單元的其他依賴項隔離。

測試的主要問題是應用程序使用共享狀態(數據庫、文件系統)。 共享狀態使我們的測試相互依賴(添加和刪除項目的測試不能並行運行)。 通過引入模擬,我們消除了測試之間的共享狀態。

有時應用程序被划分為獨立的域模塊,這些模塊通過抽象接口相互“通信”。 為了保持模塊獨立,我們將模擬通信接口,因此被測模塊將不依賴於另一個域模塊的實現細節。

從字面上模擬所有依賴項將使維護/重構更改成為一場噩夢,因為每次您將一些邏輯提取到專用類中時,您都將被迫更改/重寫您正在重構的單元的測試套件。

您的場景是一個很好的例子,通過不ItemViewModel創建,您將能夠引入一個工廠將其注入到測試下的類中並運行現有的測試套件以確保工廠不會引入任何回歸。

雖然我可以使用依賴注入輕松模擬服務,但我無法模擬通過事件添加的項目。

Misko Hevery 寫了關於這種模式的文章: How to Think about the New Operator

如果您將應用程序邏輯與圖形構造(新運算符)混合使用,那么除了應用程序中的葉節點之外,單元測試變得不可能。

因此,如果我們要查看您的問題代碼:

OnItemAdded(IItemModel addedItem)
{
  var viewModel = new ItemViewModel(addedItem);
  this.Items.Add(viewModel);
}

那么我們可以考慮的一個變化是用更間接的方法替換對ItemViewModel::new直接調用

var viewModel = factory.itemViewModel(addedItem);

factory提供了創建 ItemViewModel 的能力,而設計允許我們提供替代品。

ItemListViewModel(IService service, Factory factory)
{
  this.service.ItemAdded += this.OnItemAdded;
  this.factory = factory;
}

完成后,您可以(在適當的時候)使用 Factory 來提供一些更簡單的項目視圖模型實現。

這什么時候重要? 需要注意的一件事是您問的是 ItemViewModel,但您不是問的是List 這是為什么?

幾個答案:列表是穩定的; 我們完全不擔心 List 本身的行為會以某種方式改變,從而導致 ItemListViewModel 的行為發生可觀察到的變化。 如果測試問題后報告中,沒有將是我們介紹我們的代碼錯誤有任何疑問。

此外, this.List (大概)是孤立的。 我們不必擔心我們的測試結果會不穩定,因為同時運行了一些其他代碼。 換句話說, are test 不容易受到共享可變狀態引起的問題的影響。

如果這些屬性也適用於 ItemViewModel,那么在您的代碼中添加一堆儀式以在這兩個實現之間創建分離實際上不會使您的設計變得“更好”。

暫無
暫無

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

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