1. 程式人生 > >.NET Core TDD 前傳: 編寫易於測試的程式碼 -- 縫

.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進行依賴注入即可.

可測試的程式碼應該如下:

相關推薦

.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常情況下, 我們是先去建造汽車, 組裝好汽車後, 我們再去駕駛它. 軟件開發也類似, 我們應該把對象構造完畢之後, 再去用它.

使用Bitbucket Pipeline進行.Net Core項目的自動構建、測試和部署

net yml cimage 參考 www 模板 -c 免費 clas 1. 引言 首先,Bitbucket提供支持Mercurial和Git版本控制系統的網絡托管服務。簡單來說,它類似於GitHub,不同之處在於它支持個人免費創建私有項目倉庫。除此之外,Bitbucke

輕松掌握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

如何編寫單元測試程式碼

                                         如何編寫

如何編寫Junit測試程式碼

package com.snt.aaa.config.service.impl; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans