.NET Core TDD 前傳: 編寫易於測試的代碼 -- 縫

来源:https://www.cnblogs.com/cgzl/archive/2018/07/25/9365955.html
-Advertisement-
Play Games

.NET Core TDD 前傳: 編寫易於測試的代碼 -- 縫 為什麼要編寫易於測試的代碼? 如何創造縫隙? ...


有時候不是我們不想做單元測試, 而是這代碼寫的實在是沒法測試....

舉個例子, 如果一輛汽車在產出後沒完成測試, 那麼沒人敢去駕駛它. 代碼也是一樣的, 如果項目未能進行該做的測試, 那麼客戶就不敢去使用它, 即使使用了也會遇到“車禍”. 

 

為什麼要測試/測試的好處

  • 它可以儘早發現bug, 解決bug
  • 它會節省開發和維護一個軟體的總成本. 實際上我們在維護軟體上付出的成本要遠大於在開發時付出的成本. 開發的時候編寫單元測試確實會增加一些成本, 但是從長遠來看這些測試還是會從維護上降低軟體的總成本.
  • 它會促使開發者改進設計. 如果開發時先寫測試或者同時寫測試代碼, 那麼開發者會不得不仔細考慮要解決的問題, 所以會寫出更好的設計, 而且無需考慮如何測試代碼.
  • 相當於自成文檔. 因為所有的測試就是被開發軟體所有期待的行為.
  • 增強自信, 去除恐懼. 有時修改代碼後我們就會擔心這是否對現有的功能造成了破壞, 而如果單元測試覆蓋了軟體的重要功能的話, 那麼只要測試都能通過, 那麼就基本可以確信功能沒被破壞.

測試從不同的角度看可以分成很多類. 我們首先應該保證好單元測試能夠很好的進行, 只要單元測試能夠很好的進行, 那麼其它測試應該都可以很好的進行. 

 

為什麼要寫易於測試的代碼

再詳細說一下:

在談到軟體測試的時候, 網上的文章經常舉這個建造汽車的例子, 那我也拿汽車這個例子說明問題吧.

假設我們需要設計並生產一輛汽車, 可能會有兩種方式:

第一種是把車設計成一個複雜的整體, 把所有需要的零件都焊到了一起, 也可以說它只有一個大零件, 就是汽車本身. 這樣做的好處就是我們不必花那麼多時間和精力去製作發動機, 輪胎, 車窗等等這些可替換的零件了. 這麼去做是有可能把汽車的設計和生產成本降低的. 但是如果汽車被長期使用, 考慮到售後及維護, 那麼成本肯定會非常高了.

如果汽車壞了, 我們無法檢測是哪裡出錯, 因為它是一個整體, 無法對某部分進行隔離測試; 即使我們知道哪裡有問題, 我們還是無法替換損壞的部分, 因為它還是一個整體...

 

第二種方式就是正確的方式, 我們使用可替換的零件進行設計生產, 這樣就會方便測試和售後維護. 因為車裡的每個零件都可以被替換, 也可以取出來單獨進行測試. 如果汽車不能啟動, 那麼就對每個零件進行檢查, 最後替換出問題的零件即可, 而無需像第一種方式那樣把整個車扒開進行大修.

很明顯, 正常的汽車廠商都是使用的第二種方式, 因為其具有可測試性可維護性

 

軟體開發這個領域和設計汽車是很相似的, 可以像第一種方式一樣開發軟體, 也可以像第二種方式一樣開發軟體.

在現實中, 有太多的開發者使用了第一種方式, 把一大堆代碼和功能都放到了一起. 而實際上開發者們應該採用第二種方式來進行代碼的設計和編寫, 即使在開發初期這可能會花掉更多的時間和精力. 

有的時候不是開發者不想採取第二種方式, 而是花了很大力氣卻發現寫出來的代碼仍然不能很好的進行單元測試, 所以實際問題是不知道該如何寫出易於測試的代碼.

 

什麼樣的代碼易於測試

還是汽車的例子, 如果我們懷疑汽車的電瓶壞了, 那麼採用第一種方式創造的汽車就無法進行對它的“電瓶”進行單獨檢測, 因為是焊到一起的, 也沒有可以用檢測的插頭等; 而採用第二種方式建造的汽車則可以把電瓶拿出來, 然後我們使用電壓表等專用的儀器在隔離的情況下對其進行檢測.

第二種方式之所以可以進行隔離測試是因為它採用的是可替換零件, 也就是零件可以拿下來.

用專業的術語說就是第二種方式里有縫(seam). 在軟體里, 什麼是縫(seam)? 縫就是你可以在程式里替換行為的地方, 而不需要在這個地方進行修改. 或者說就是可以讓你的代碼移除依賴項並創建出可用於隔離測試對象的地方.....我可能解釋的不明白, 看圖吧:

虛線就是縫.

 

由於有縫的存在, 所以我們可以進行隔離測試:

分別使用Test FixtureTest double來替換調用類和依賴項.

而採用第一種方式的軟體就無法把代碼拆出來進行測試了, 因為無法替換依賴項, 無法接入到測試環境, 也就是說無法進行隔離測試了.

 

為什麼代碼會無法進行隔離測試呢

無法測試的代碼有一些特點:

  • new 關鍵字. 如果這部分代碼里出現了new關鍵字, 也就是說在構造函數或方法內創造了外部資源或較複雜類型的實例, 那麼測試就會很困難了. 而應該採用的做法是依賴註入.
  • 靜態方法/屬性調用. 靜態方法會為它的調用者和它被調用時所在的類創建很緊的耦合. 使用像Math.Min(), String.Join()這些方法時是沒有題的, 但是如果使用DateTime.Now, Console.Write() 那就可能會出問題了. 這時候你可能就需要使用一個包裝類了.
  • 單立體 Singleton. Singleton的本質是共用狀態. 但是為了隔離測試, 最好還是避免使用singleton. 如果確實需要使用它的話, 那麼在測試的時候可以使用一個非Singleton的替身來進行測試, 當然, 通過依賴註入.
  • 全局共用狀態, 這個應該明白
  • 引用第三方框架或外部資源. 一旦有這樣的引用的話, 就無法進行隔離測試了. 我們需要做的就是對這些東西抽象化, 把細節忽略只關心特定條件下的特定結果.

 

如何產生縫隙

  • 解藕依賴項. 在C#里, 我們通過對介面編程而不是對實現來編程來實現這個任務. 
  • 依賴註入. 主要是採用構造函數註入.

做到這兩點, 那麼我們就可以使用test double(測試替身)來代替依賴項並註入到被測試類使用, 從而進行隔離測試.

 

例子

下麵就是一個難以測試的例子, 這個代碼並不完美, 無法展示出不可測試代碼所有的特點, 但是也包含了至少兩個特點:

首先它的依賴項都是new出來的, 這些依賴項就有依賴於資料庫的, 所以測試的話, 我們還需要知道資料庫裡面特定的數據內容..這樣的結果就是測試很難完成.

其次這裡用到了第三方的Mapper.Map()靜態方法, 這個方法也許是經過測試的並且沒有副作用的, 但是也有可能不是. 而且它造成了ProductControllerHard和Mapper類之間的緊耦合.

 

針對第一個問題, 我想都知道怎麼去處理了, 就是使用介面. 我就不多介紹了.

針對第二個問題, 使用靜態方法造成了緊耦合. 如果這個靜態方法是我們自己寫的方法, 我們可以對其重構, 變成實例方法. 但是如果它來自第三方庫, 並且第三方庫沒有提供可以依賴註入使用的版本, 那麼我們自己可以寫一個包裝類(wrapper)來包裝該方法:

但是由於這個Mapper來自AutoMapper庫, 這個庫提供了IMapper介面, 所以使用IMapper進行依賴註入即可.

 

可測試的代碼應該如下:

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一, 繼承 繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為 基類或超類,新建的類稱為派生類或子類 1. python中類的繼承分為:單繼承和多繼承 2. 查看繼承 提示:如果沒有指定基類,python的類會預設繼承object類,object是所有pytho ...
  • 對象操作流(序列化流) 每次讀取和寫出的都是JavaBean對象. 序列化:將對象寫入到文件中的過程 反序列化:從文件中讀取對象到程式的過程 transient: 標識瞬態,序列化的時候,該修飾符修飾的成員不能序列化 ObjectOutputStream 構造方法: public ObjectOut ...
  • 要想學習java語言,首先要搭建Java的開發環境,包括開發環境和運行環境,那就要下載jdk的安裝包來進行搭建了 下載地址:鏈接: https://pan.baidu.com/s/1msUuHYRfIjxyPnwZEksNRw 密碼: ssv4 首先我們先來瞭解一下JDK JDK:java deve ...
  • """關係型資料庫(SQL資料庫):MySQL、Oracle、Sqlite3、SQLServer...1.可以存儲統一格式的數據2.可以用於保存大量的數據3.表與表之間有關聯 非關係型數據(NoSQL資料庫):Mangodb、Redis....""" # 1.建立資料庫連接# connect() 若 ...
  • 1. 重新排列數列,使得數組左邊為奇數,右邊為偶數 空間複雜度O(1) 時間複雜度O(n) 思路:兩個指針分別指向數組的頭和尾,頭指針正向遍曆數組,找到第一個偶數 尾指針反向遍曆數組,找到第一個奇數,兩者交換 2. 如何找出數組中唯一的重覆元素 每個數組只能訪問一次,不能用輔助空間 數組a[n] 數 ...
  • 正如我們在《控制反轉》提到過的,很多人將IoC理解為一種“面向對象的設計模式”,實際上IoC自身不僅與面向對象沒有必然的聯繫,它也算不上是一種設計模式。一般來講,設計模式提供了一種解決某種具體問題的方案,但是IoC既沒有一個針對性的問題領域,其自身沒有提供一種可實施的解決方案,所以我更加傾向於將Io... ...
  • 基於Ocelot、Consul、Registrator搭建微服務架構,實現服務發現和自動註冊,後續逐步完善許可權驗證、負載均衡等功能。 ...
  • 某些業務需要根據不同的功能將日誌記錄到不同的位置,以便於區分。 日誌工具類(這裡只是簡單的封裝): 瀏覽頁面後,可以看到日誌目錄如下: 參考資料:https://stackoverflow.com/questions/11930381/log4net-multiple-appenders-writi ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...