有時候不是咱們不想作單元測試, 而是這代碼寫的實在是無法測試....數據庫
舉個例子, 若是一輛汽車在產出後沒完成測試, 那麼沒人敢去駕駛它. 代碼也是同樣的, 若是項目未能進行該作的測試, 那麼客戶就不敢去使用它, 即便使用了也會遇到「車禍」. 編程
測試從不一樣的角度看能夠分紅不少類. 咱們首先應該保證好單元測試可以很好的進行, 只要單元測試可以很好的進行, 那麼其它測試應該均可以很好的進行. app
再詳細說一下:框架
在談到軟件測試的時候, 網上的文章常常舉這個建造汽車的例子, 那我也拿汽車這個例子說明問題吧.函數
假設咱們須要設計並生產一輛汽車, 可能會有兩種方式:單元測試
第一種是把車設計成一個複雜的總體, 把全部須要的零件都焊到了一塊兒, 也能夠說它只有一個大零件, 就是汽車自己. 這樣作的好處就是咱們沒必要花那麼多時間和精力去製做發動機, 輪胎, 車窗等等這些可替換的零件了. 這麼去作是有可能把汽車的設計和生產成本下降的. 可是若是汽車被長期使用, 考慮到售後及維護, 那麼成本確定會很是高了.測試
若是汽車壞了, 咱們沒法檢測是哪裏出錯, 由於它是一個總體, 沒法對某部分進行隔離測試; 即便咱們知道哪裏有問題, 咱們仍是沒法替換損壞的部分, 由於它仍是一個總體...spa
第二種方式就是正確的方式, 咱們使用可替換的零件進行設計生產, 這樣就會方便測試和售後維護. 由於車裏的每一個零件均可以被替換, 也能夠取出來單獨進行測試. 若是汽車不能啓動, 那麼就對每一個零件進行檢查, 最後替換出問題的零件便可, 而無需像第一種方式那樣把整個車扒開進行大修.設計
很明顯, 正常的汽車廠商都是使用的第二種方式, 由於其具備可測試性和可維護性. 3d
軟件開發這個領域和設計汽車是很類似的, 能夠像第一種方式同樣開發軟件, 也能夠像第二種方式同樣開發軟件.
在現實中, 有太多的開發者使用了第一種方式, 把一大堆代碼和功能都放到了一塊兒. 而實際上開發者們應該採用第二種方式來進行代碼的設計和編寫, 即便在開發初期這可能會花掉更多的時間和精力.
有的時候不是開發者不想採起第二種方式, 而是花了很大力氣卻發現寫出來的代碼仍然不能很好的進行單元測試, 因此實際問題是不知道該如何寫出易於測試的代碼.
仍是汽車的例子, 若是咱們懷疑汽車的電瓶壞了, 那麼採用第一種方式創造的汽車就沒法進行對它的「電瓶」進行單獨檢測, 由於是焊到一塊兒的, 也沒有能夠用檢測的插頭等; 而採用第二種方式建造的汽車則能夠把電瓶拿出來, 而後咱們使用電壓表等專用的儀器在隔離的狀況下對其進行檢測.
第二種方式之因此能夠進行隔離測試是由於它採用的是可替換零件, 也就是零件能夠拿下來.
用專業的術語說就是第二種方式裏有縫(seam). 在軟件裏, 什麼是縫(seam)? 縫就是你能夠在程序裏替換行爲的地方, 而不須要在這個地方進行修改. 或者說就是可讓你的代碼移除依賴項並建立出可用於隔離測試對象的地方.....我可能解釋的不明白, 看圖吧:
虛線就是縫.
因爲有縫的存在, 因此咱們能夠進行隔離測試:
分別使用Test Fixture和Test double來替換調用類和依賴項.
而採用第一種方式的軟件就沒法把代碼拆出來進行測試了, 由於沒法替換依賴項, 沒法接入到測試環境, 也就是說沒法進行隔離測試了.
沒法測試的代碼有一些特色:
作到這兩點, 那麼咱們就可使用test double(測試替身)來代替依賴項並注入到被測試類使用, 從而進行隔離測試.
下面就是一個難以測試的例子, 這個代碼並不完美, 沒法展現出不可測試代碼全部的特色, 可是也包含了至少兩個特色:
首先它的依賴項都是new出來的, 這些依賴項就有依賴於數據庫的, 因此測試的話, 咱們還須要知道數據庫裏面特定的數據內容..這樣的結果就是測試很難完成.
其次這裏用到了第三方的Mapper.Map()靜態方法, 這個方法也許是通過測試的而且沒有反作用的, 可是也有可能不是. 並且它形成了ProductControllerHard和Mapper類之間的緊耦合.
針對第一個問題, 我想都知道怎麼去處理了, 就是使用接口. 我就很少介紹了.
針對第二個問題, 使用靜態方法形成了緊耦合. 若是這個靜態方法是咱們本身寫的方法, 咱們能夠對其重構, 變成實例方法. 可是若是它來自第三方庫, 而且第三方庫沒有提供能夠依賴注入使用的版本, 那麼咱們本身能夠寫一個包裝類(wrapper)來包裝該方法:
可是因爲這個Mapper來自AutoMapper庫, 這個庫提供了IMapper接口, 因此使用IMapper進行依賴注入便可.
可測試的代碼應該以下: