假設我們有一個類Controller
,它取決於類Service
,而Service
類取決於類Repository
。只有Repository
與外部系統(比如數據庫)進行通信,我知道它應該在單元測試執行時被模擬。我應該在單元測試中嘲笑類的所有直接依賴關係嗎?
我的問題:對於單元測試,當Controller
類被測試時,即使Service
類不直接依賴於任何外部系統,我應該嘲笑Service
類嗎?爲什麼?
假設我們有一個類Controller
,它取決於類Service
,而Service
類取決於類Repository
。只有Repository
與外部系統(比如數據庫)進行通信,我知道它應該在單元測試執行時被模擬。我應該在單元測試中嘲笑類的所有直接依賴關係嗎?
我的問題:對於單元測試,當Controller
類被測試時,即使Service
類不直接依賴於任何外部系統,我應該嘲笑Service
類嗎?爲什麼?
這取決於您正在編寫的測試類型:集成測試或單元測試。 我假設你想在這種情況下編寫一個單元測試。單元測試的目的是單獨測試您的類的業務邏輯,因此應該嘲笑每個其他依賴項。
在這種情況下,您將模擬Service
類。這樣做還允許您根據您傳遞給Service
的某種方法的輸入準備測試某些情景。想象一下你的Service
中有一個Person findPerson(Long personID)
-方法。在測試您的Controller
時,您並不想對Service
實際返回正確的輸出做所有必要的事情。對於您的Controller
的某個測試場景,您只需要它返回Person
,而對於不同的測試場景,您不希望它返回任何內容。嘲笑使這非常容易。
還要注意的是,如果你嘲笑你Service
你不必嘲笑Repository
因爲你Service
已經是一個模擬。
TLDR;在爲某個類編寫單元測試時,只需模擬其他每個依賴項即可操作對這些依賴項進行方法調用的輸出。
是的,模擬測試控制器時的服務。單元測試有助於確定迴歸的位置。因此,如果服務代碼已更改,則服務代碼的測試只應失敗,而不是控制器代碼已更改。這樣,當服務測試失敗時,您肯定知道根本原因在於對服務的更改。
另外,通常模擬服務比模擬服務調用的所有存儲庫僅測試控制器要容易得多。所以它使您的測試更容易維護。
但是總的來說,你可能會保留某些util類,因爲你嘲笑這些類會比獲得的更多。另請參閱: https://softwareengineering.stackexchange.com/questions/148049/how-to-deal-with-static-utility-classes-when-designing-for-testability
與所有工程問題一樣,TDD也不例外。答案總是,「取決於」。總是存在折衷。
在TDD的情況下,您首先通過行爲期望開發測試。根據我的經驗,行爲期望是一個單位。
舉個例子,假設你想讓所有以姓氏'A'開頭的用戶在系統中處於活動狀態。所以你會寫一個測試來創建一個控制器動作來獲得以'A'public ActionResult GetAllActiveUsersThatStartWithA()
開頭的活動用戶。
最後,我可能有這樣的事情:
public ActionResultGetAllActiveUsersThatStartWithA()
{
var users = _repository.GetAllUsers();
var activeUsersThatStartWithA = users.Where(u => u.IsActive && u.Name.StartsWith('A');
return View(activeUsersThatStartWithA);
}
這對我來說是一個單位。然後我可以現在重構(更改我的實現而無需添加service
類與下面的方法改變行爲)
public IEnumerable<User> GetActiveUsersThatStartWithLetter(char startWith)
{
var users = _repository.GetAllUsers();
var activeUsersThatStartWithA = users.Where(u => u.IsActive && u.Name.StartsWith(startsWith);
}
而且我的新實現該控制器變得
public ActionResultGetAllActiveUsersThatStartWithA()
{
return View(_service.GetActiveUsersThatStartWithLetter('A');
}
這顯然是一個很做作例如,但它給出了我的觀點。這樣做的主要好處是,除了repository
之外,我的測試不受限於任何實現細節。然而,如果我在測試中嘲笑了service
,我現在被綁定到該實現。如果由於某種原因service
圖層被刪除,我的所有測試都會中斷。我會發現service
層比repository
層更易於改變。
另一件要考慮的事情是,如果我在我的控制器類中剔除service
,我可能遇到一種情況,即所有測試都正常工作,但我發現系統損壞的唯一方法是通過集成測試(意味着超出流程,或裝配組件相互影響),或者通過生產問題。
例如如果我改變service
類的實現以下:
public IEnumerable<User> GetActiveUsersThatStartWithLetter(char startsWith)
{
throw new Exception();
}
再次,這是一個很做作的例子,但問題仍然是相關的。我不會在我的controller
測試中看到這一點,因此它看起來像系統通過我的「單元測試」傳遞正常,但實際上系統根本不工作。
我的方法的缺點是,測試可能會變得非常繁瑣的設置。因此,權衡是將測試複雜性與抽象/可摹擬實現相平衡。
要記住的關鍵是TDD提供捕捉迴歸的好處,但它的主要好處是幫助設計系統。換句話說,不要讓設計決定你寫的測試。讓測試首先確定系統的功能,然後通過重構來擔心設計。
你的假設是正確的:我想寫單元測試。現在我已經知道單元測試應該如何了。非常感謝你! – umainyosu