.NET Core TDD 前傳: 編寫易於測試的程式碼 -- 構建物件
該系列第1篇: 講述了如何創造"縫". "縫"(seam)是需要知道的概念.
本文是第2篇, 介紹的是如何避免在構建物件時寫出不易測試的程式碼. 本文的概念性內容大部分都來自Misko Hevery的這篇部落格文章.
構建
還是用上文裡汽車的例子.
通常情況下, 我們是先去建造汽車, 組裝好汽車後, 我們再去駕駛它.
軟體開發也類似, 我們應該把物件構造完畢之後, 再去用它. 但是有時候, 開發者會在構造過程中新增一些程式邏輯. 這就相當於車還沒造完, 我們就駕駛它去兜風了. 這樣做是不太好的.
建構函式是類用來建立其例項物件的方法, 這裡的程式碼是用來準備該物件的. 但有時開發者會在建構函式裡做一些其它的工作, 例如構建依賴項, 執行初始化邏輯等等.
在建構函式(或者更大一點, 指構建的過程)裡, 做這些額外的工作會讓測試變得異常困難. 這是因為像初始化依賴項, 呼叫服務, 設定狀態的邏輯等這些工作會把用於測試的"縫"弄丟. 導致無法進行mock.
總之在構造的過程中做太多的工作會妨礙測試.
危險訊號
- 在建構函式/欄位聲明裡出現new關鍵字
- 如果建構函式裡需要建立依賴, 那麼這就會為該類與依賴項之間創造了緊耦合. 這個之前提過, 所以需要注入依賴. 但是簡單的值型別, 例如字串, List, Dictionary等還是可以的.
- 在建構函式/欄位聲明裡呼叫靜態方法
- 靜態方法不可以被mock, 也不能被注入.
- 構造函數出現流程控制邏輯程式碼
- 這樣就很難對邏輯直接進行測試了. 我們只能分別使用不同的方式構造該物件, 測試並確認物件的狀態. 而這個狀態通常對直接測試是隱藏的. 實際上只要不是賦值程式碼, 就有可能是問題程式碼.
- 建構函式裡出現非賦值程式碼
- 存在另外一個初始化函式 (也就是說建構函式走了完, 但是物件並沒有被完全初始化)
如何解決問題?
- 不要在建構函式裡建立依賴項, 應該注入它們. 然後在建構函式裡把它們賦值給類的私有變數.
- 當需要構建物件圖(一組有引用關係的物件), 也包括物件需要一些構建的引數等情況, 應該使用工廠, 建造者模式, 或者IoC容器的依賴注入
- 避免在建構函式裡寫邏輯程式碼, 例如條件, 迴圈, 計算等等. 也不能把邏輯程式碼放在別的方法, 然後呼叫該方法...
總之就是要避免物件的構建和物件的行為混合到一起, 因為它們在一起就會很難進行測試.
最後還有一點, 首先你需要知道, 根據angular的創始人Misko Hevery所說:
物件的構造分兩類, 一種是可注入的, 一種是可new的.
可注入的物件可以由其它的一堆可注入物件組成. 它們可以為 可new的 物件工作. 可注入的物件通常是實現了介面的service, 像什麼IUnitOfWork, IRepository, IxxxService等等.
可new的物件就是物件圖裡的終點, 例如實體或者值物件(Value Object)等.
為了易於測試, 針對這兩類構造, 有下列規則:
可注入的物件可以在建構函式請求(注入)其它的可以注入物件, 但是不能在建構函式請求可new的物件.
反過來, 可new的物件可以在建構函式請求其它的可new物件, 但是不能在建構函式請求可注入的物件.
例子
第一個例子
這是不對的, 構建的過程中直接new的話, 就會造成緊耦合, 也無法在測試中使用Test Double來代替它們了. 如果測試中不代替它們的話, 有些服務的開銷可能會很大.
正確的寫法是使用依賴注入:
第二個例子
該例中, UserController只需要UserService和LoggingService兩個依賴項. 但是UserService又依賴於UserRepository.
但是這樣寫就不對了, 這會造成UserController和UserRepository間的緊耦合, 而且配置UserService也並不是UserController的責任.
正確的寫法是:
而UserService也最好是注入依賴.
而如果UserService並不是在建構函式注入UserRepository的話:
那麼Controller裡就應該這樣寫:
不過最好還是使用建構函式注入的寫法.
第三個例子
仔細的說, 該例有不止一處錯誤.
首先它有條件判斷邏輯程式碼; 此外它還使用了ApplicationState.IsRunning這個靜態變數(就是全域性狀態); 而且在建構函式裡還做了UserService的配置工作, 這不是UserController的責任.
儘量要避免全域性變數, 它無法進行隔離, 測試會遇到麻煩, 例如並行測試時其中一個測試改變了靜態變數的值就可能導致另一個測試失敗.
但是粗略的說, 該例可以說就是一個錯誤, 如何配置UserService並不是UserController的責任, 所以, 正確的做法是把UserService配置相關的程式碼移出去, 讓它自己去管理吧:
第四個例子
該例子中, LoggingService的Log方法需要一個Area型別的物件, 它是一個值物件.
所以它的錯誤就是, 不應該把可new的物件注入到可注入的物件裡. 這麼做的話, 測試就不好做隔離了.
正確的做法應該是, 作為方法的引數傳遞進來:
第五個例子
如果出現類類似initalize()或類似意思的方法, 很有可能說明該物件的責任太多了.
修改它很簡單, 讓各自的類負責自己的內容即可. 去掉initialize()方法即可.
例子就舉這些, 並不全, 詳細請看Angular作者的博文.
測試/執行時如何建立物件
上面例子裡的UserController就是我們需要使用的物件, 在執行時, 程式碼可能是這樣的:
構建這個物件還是有點麻煩的, 它的類關係圖如下:
所以測試的設定過程也會比較麻煩:
當然也可以不直接new, 而是使用mock. 總之都很麻煩.
使用工廠
所以我們可以使用Factory等模式, 把構建UserController的工作放到工廠裡:
可以這樣呼叫:
使用IoC容器
如果專案使用了IoC容器的話, 還可以使用類似下面的用法:
先介紹到這裡.
相關推薦
.NET Core TDD 前傳: 編寫易於測試的代碼 -- 縫
廠商 關鍵字 com omap 註入 大堆 解決 而是 不知道 有時候不是我們不想做單元測試, 而是這代碼寫的實在是沒法測試.... 舉個例子, 如果一輛汽車在產出後沒完成測試, 那麽沒人敢去駕駛它. 代碼也是一樣的, 如果項目未能進行該做的測試, 那麽客戶就不敢去使用它
.NET Core TDD 前傳: 編寫易於測試的代碼 -- 構建對象
rep 文章 建立 ini 代碼 ali 請求 uid 依賴項 該系列第1篇: 講述了如何創造"縫". "縫"(seam)是需要知道的概念. 本文是第2篇, 介紹的是如何避免在構建對象時寫出不易測試的代碼. 本文的概念性內容大部分都來自Misko Hevery的這篇博客
.NET Core TDD 前傳: 編寫易於測試的程式碼 -- 構建物件
該系列第1篇: 講述了如何創造"縫". "縫"(seam)是需要知道的概念. 本文是第2篇, 介紹的是如何避免在構建物件時寫出不易測試的程式碼. 本文的概念性內容大部分都來自Misko Hevery的這篇部落格文章. 構建 還是用上文裡汽車的例子. 通常情況下, 我們是先去建造汽車, 組裝好汽車後,
.NET Core TDD 前傳: 編寫易於測試的程式碼 -- 縫
有時候不是我們不想做單元測試, 而是這程式碼寫的實在是沒法測試.... 舉個例子, 如果一輛汽車在產出後沒完成測試, 那麼沒人敢去駕駛它. 程式碼也是一樣的, 如果專案未能進行該做的測試, 那麼客戶就不敢去使用它, 即使使用了也會遇到“車禍”. 為什麼要測試/測試的好處 它可以儘早發現bug,
.NET Core TDD 前傳: 編寫易於測試的程式碼 -- 單一職責
第1篇: 講述了如何創造"縫". "縫"(seam)是需要知道的概念. 本文是第5篇, 也是最後一篇, 介紹的是單一職責 類做了太多的工作 例子, 某軟體公司, 原有專案開發, 測試, 售前, 售後, 財務等員工. 後來由於公司沒錢, 裁掉了測試, 讓開發兼職; 過了段時間, 又裁掉了
.NET Core TDD 前傳: 編寫易於測試的程式碼 -- 全域性狀態
第1篇: 講述了如何創造"縫". "縫"(seam)是需要知道的概念. 本文是第4篇, 將介紹全域性狀態引起的問題. 全域性狀態 全域性狀態, 也可以叫做應用程式狀態, 它是一組變數, 這些變數維護著應用程式的高階狀態. 在程式裡, 全域性狀態可能都存放在一個全域性狀態物件裡, 例如AS
.NET Core TDD 前傳: 編寫易於測試的程式碼 -- 依賴項
第1篇: 講述了如何創造"縫". "縫"(seam)是需要知道的概念. 本文是第3篇, 講述依賴項和迪米特法則. 迪米特法則 (Law of Demeter) 還是使用建造汽車的例子. 生產汽車的時候需要輪胎, 組裝時需要什麼型號的輪胎, 就請求該型號的輪胎, 然後相關人員會從庫房把該型號的輪
.NET Core TDD 前傳OA 信用盤平臺搭建: 編寫易於測試的代碼構建對象
建造者 五個 包括 值類型 bject 方法 之前 關系 服務 聯系方式:QQ:2747044651 網址http://zhengtuwl.com常情況下, 我們是先去建造汽車, 組裝好汽車後, 我們再去駕駛它. 軟件開發也類似, 我們應該把對象構造完畢之後, 再去用它.
輕松掌握VS Code開發.Net Core及創建Xunit單元測試
blog logs 寫文章 編譯 分享 單獨 etc 2.0 ren 前言 本篇文章主要還是介紹使用 VS Code 進行.Net Core開發和常用 CLI命令的使用,至於為啥要用VS Code ,因為它是真的是好看又好用 :) ,哈哈,主要還是為了跨平臺開發做準備。 開
學習 ASP.NET Core 2.1:集成測試中使用 WebApplicationFactory
UNC enc sta 測試 修改 構造 creat -a msdn WebApplicationFactory 是 ASP.NET Core 2.1 新特性 MVC functional test infrastructure 中帶來的新東東,它封裝了 TestServe
ASP.NET Core 入門教程 5、ASP.NET Core MVC 檢視傳值入門
一、前言 1、本教程主要內容 ASP.NET Core MVC 檢視引擎(Razor)簡介 ASP.NET Core MVC 檢視(Razor)ViewData使用示例 ASP.NET Core MVC 檢視(Razor)ViewBag使用示例 ASP.NET Core NVC 檢視(Razor)強型別傳值
ASP NET Core檔案上傳與下載 多種上傳方式
前言前段時間專案上線,實在太忙,最近終於開始可以研究研究ASP.NET Core了.打算寫個系列,但是還沒想好
ASP.NET Core檔案上傳與下載(多種上傳方式)
前言前段時間專案上線,實在太忙,最近終於開始可以研究研究ASP.NET Core了.打算寫個系列
.Net Core 檔案上傳與下載
參考連結: 遇到的問題: 按 參考1 中測試,下載檔案檔名總是變成方法名(DownloadFile),並且沒有副檔名,儲存後改副檔名可正常檢視。 參考 連結3 測試無效,未解決下載問題。 參考 連結2,問題解決。 程式碼實現如下: 檔案上傳 [
.NET CORE與Spring Boot編寫控制檯程式應有的優雅姿勢
本文分別說明.NET CORE與Spring Boot 編寫控制檯程式應有的“正確”方法,以便.NET程式設計師、JAVA程式設計師可以相互學習與加深瞭解,注意本文只介紹用法,不會刻意強調哪種語言或哪種框架寫的控制檯程式要好。 本文所說的編寫控制檯程式應有的“正
.NetCore技術研究-.NET Core遷移前的準備工作
前段時間遷移.NET Core做了大量的試水和評估,今天整理一下分享給大家。大致有以下幾個部分: 1. .NET Core的由來 2. 為什麼要遷移.NET Core 3. .NET Core3.X主要特性 4. .NET Standard和.NET Core 5. .NET Core Roadma
asp.net core 使用 TestServer 來做整合測試
# asp.net core 使用 TestServer 來做整合測試 ## Intro 之前我的專案裡的整合測試是隨機一個埠,每次都真實的啟動一個 WebServer,之前也有看到過微軟文件上 `TestServer` 的介紹,當時沒仔細看過以為差不多就沒用,一直是啟動了一個真正的 WebServer
Core中使用Hangfire 在Asp.Net Core中使用DI的方式使用Hangfire構建後臺執行指令碼 解決 ASP.NET Core Hangfire 未授權(401 Unauthorized)
之前使用Quartz.Net,後來發現hangfire對Core的繼承更加的好,而且自帶管理後臺,這就比前者好用太多了。 安裝註冊 安裝 PM> Install-Package Hangfire Startup.cs,在ConfigureServices方法中添加註冊:
.NET Core的檔案系統[4]:由EmbeddedFileProvider構建的內嵌(資源)檔案系統
一個物理檔案可以直接作為資源內嵌到編譯生成的程式集中。藉助於EmbeddedFileProvider,我們可以統一的程式設計方式來讀取內嵌於某個程式集中的資原始檔,不過在這之前我們必須知道如何將一個專案檔案作為資源並嵌入到生成的程式集中。 [ 本文已經同步到《ASP.NET Core框架揭祕》之中] 目錄一
.NET Core的檔案系統[3]:由PhysicalFileProvider構建的物理檔案系統
ASP.NET Core應用中使用得最多的還是具體的物理檔案,比如配置檔案、View檔案以及網頁上的靜態檔案,物理檔案系統的抽象通過PhysicalFileProvider這個FileProvider來實現,該型別定義在NuGet包“Microsoft.Extensions.FileProviders.Phy