天天看點

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

閱讀目錄:

1.開篇介紹 

2.疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

2.1.面向接口程式設計的兩個設計誤區

2.1.1.接口的依賴倒置

2.1.2.接口對實體的抽象 

2.2.疊代單元測試、重構(代碼可測試)

2.2.1.LINQ表達式對單元測試的影響 

最近一段時間結束了一個小項目的開發,覺得有些好東西值得總結與分享,是以花點時間整理成文章;

大多數情況下我們都知道這些概念,面向接口程式設計是老生常談的話題了,有幾年程式設計經驗的都知道怎麼運用;單元測試其實在前幾年不怎麼被重視,然而最近逐漸的浮現在我們眼前,而且被提起的頻率也大了很多了,包括重構、可測試性都慢慢的貼近我們,我們隻有親自動手去使用它才能領悟其精髓;

下面我将總結一下我對上述幾個概念之間的新體會;

面向接口程式設計要求我們彼此之間使用接口的方式調用,将一切可能存在變化的執行個體隔離在内部,這些執行個體都隻是一個可以随時被替換的幕後勞動者;但是面向接口程式設計是需要一定的設計能力,能否合理的将對象抽象出接口來,真是一句兩句話無法概括的;

面向接口設計其實本人覺得會有一些細節的設計誤區,既然抽象出接口那麼就存在接口依賴的問題,還有就是對于Entity類型的抽象是否合理,是否會打亂Entity的清晰度,因為我們對DomainModel的了解是DomainEntity是一個POCO的對象,就是一個很簡單的純淨的類實體,一目了然,如果換成接口對後面的DDD的開發會有很大的麻煩,因為對接口的支援無法做到簡單的持久化,還有就是思維上的轉變也有很大的麻煩;

首先我覺得第一個誤區就是接口的依賴問題,接口的依賴不是一個小問題,在真實的項目中層之間的依賴是有嚴格的要求的,傳統分層架構要求上層隻能夠依賴下層,而DDD分層架構是DomaiModel層絕對的無任何依賴,DomainModel不會去引用下層的基礎設施,因為它要求絕對的幹淨;但是發現還是有很多的項目沒有能夠了解DDD的這點優點;然後就是對于層之間的實體抽取接口,其實這點真的有待商量,DataAccess Layer中的資料實體嚴格意義說是DTO對象是用來過度到Business Layer中使用的,那麼如果将DataAccess中的DTO設計成接口類型對外提供使用,Business Layer 就依賴上了DataAccess Layer了,是以還是需要根據項目的具體需求來平衡,下面我們看一下示例及分析;

傳統的三層架構,在Facade中調用BLL的方法,BLL調用DAL方法,這難道不是違背了“單一職責”原則嗎;一直我們都在強調“單一職責”設計原則,為什麼很多項目的每層之間都是直接使用下層的接口,特别是我們的核心DomainModel層中,本來就是很幹淨的純業務處理,來一個什麼資料通路的接口真的很不美;

圖1:

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

這種架構應該是大部分的項目的結構,我們應該一眼就看出問題在哪裡了,很明顯在Bl Layer中直接使用了Da Layer 相關接口擷取資料,單純從這一點就有點違背單一職責設計原則;

圖2:

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

接口依賴倒置到底是誰向誰倒置了,第一張圖是業務層依賴了資料層,詳細點就是依賴了資料通路的接口;第二張圖中業務層沒有依賴任何東西,細心的朋友應該看到第二張圖中多了一個“DomainModel Event route ” 的東西,這是一種機制,目的是讓領域内部産生領域事件,類似事件路由的效果,基礎設施要做任何的事情跟DomaiModel Entity 本身沒有任何關系;

實體的抽象如果變成接口會很别扭,我們對實體的最直覺的認識是一個很POCO的對象,但是如果你在設計的時候将資料通路的DTO都設計成接口是否是有點不必要,有兩個情況下可以平衡這種需要,第一如果你的DTO不需要業務層傳入資料層那麼無所謂的,那麼如果是需要業務層傳入資料層的接口肯定是不行的,這裡就是覺得将實體與接口的概念扯到一起很不直覺,像業務實體你把它抽層接口對持久化來說就是一個問題了;

其實這篇文章的主要内容是在這一節,上一節我說了一下我對接口抽象的一點個人看法;這一節我們将通過一個具體的示例來看一下這篇文章的重要内容,看看單元測試如何與持續疊代重構完美結合的,在編寫單元測試用例的時候我們将發現代碼被逐漸的重構的很優美,面向接口程式設計再一次被提到一個高度;

在我們編寫代碼的時候一般情況下無法驗證我們的代碼好與壞,光憑嘴說也很難斷定每個人的設計思路是否完全正确的,是以代碼可測試性将成為驗證你所編寫的代碼的品質的一個重要名額;

單元測試與重構将是一個持續疊代的過程,很多人并不太關心重構和單元測試,其實是因為我們大部分情況下在開發一次性的傳遞的項目而不是持續更新的産品,是以單元測試、重構被我們所忽視,面向接口程式設計也被我們時而記起也時而忘記,下面我們來看一下如何編寫可測試性的代碼;

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)
.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

View Code

這是一個很簡單的靜态類,主要目的是模拟根據查詢條件從伺服器上查詢相關的報表資訊,由于這裡是為了示範是以直接傳回了Report對象,隻是作為執行個體示範,Report是作為報表對象的抽象,沒有任何的資料字段;

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)
.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

這是一個執行個體類,用來對遠端傳回的表達進行分析,就好比一個業務一個資料通路,隻不過這裡的資料通路大部分情況下我們都會使用靜态類來實作;

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)
.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

這個就是程式調用的地方,用來模拟程式運作時的入口,可以當成是Application Layer中的Facade對象;

其實這裡就能看出來我在2.1】小結中說的“單一職責”設計原則,我已經将資料通路代碼在ReportAnalyse中使用了,其實這裡是不對的,應該是在外部裝載好然後傳入ReportAnalyse中才對,才符合單一職責設計原則,當然這裡不是講它,是以不扯了;

我們假設上面的代碼已經完成了對Report對象的分析了,下面我們需要對代碼進行單元測試,主要是兩個類ReportAnalyse、ServiceReport,我們先從ReportAnalyse類開始吧;

【單元測試】

建立基本的單元測試項目,然後記得引用被測試項目,最後建立一個用來測試ReportAnalyse類的單元測試檔案;

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)
.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

寫上很簡單的測試用例,這裡的主要目的不是怎麼寫測試用例,也不是怎麼測試代碼,這裡的目的是如何進行單元測試、重構等疊代的過程,是以如何寫用例不是重點,這裡直接帶過了;

圖3:

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

如果沒有問題的話,這個單元測試用例肯定是過的,因為沒有其他什麼邏輯,很簡單的兩行代碼;看起來一起很好,沒有問題,單元測試也通過了,這個時候我們放心的去做其他的功能了,但是過了幾天發現自己的ReportAnalyse單元測試突然不過了,後來檢查發現有人改了ServiceReport實作,原本從本地直接執行個體化的Report現在需要配置過後才能使用,也就是說你這個時候測試不了你的代碼了,以為你的ReportAnalyse會随時受到ServiceReport的影響,但是這個問題如果在運作時是無所謂的,畢竟在産線上都是配置好的;

這個時候就會是牽一發而動全身的困境,因為我們的代碼是面向實作程式設計的,也就是說耦合度很高,這個時候我們需要根據需要對ServiceReport進行适當的重構,當然重構的首要目标就是将它與任何實作脫耦;

下面我們将ServiceReport提取出一個接口,然後通過IOC的方式動态的注入進來就實作了完全的脫耦;

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)
.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

這裡的構造函數當然不是直接執行個體化的,需要使用相關的IOC架構做支撐;我們看一下上面的代碼很簡潔,依賴IServiceReport接口,這個時候我們再回過頭來對單元測試進行簡單的修改來适應可以持續重構的代碼;

為了使代碼好測試點,我修改了一下Analyse方法;

圖4:

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

畫紅線的部分在我們沒有進行重構之前是會随着ServiceReport的變化而變化的,但是被我們抽象成接口之後就變的很容易測試了,我們自己可以任何控制它的傳回值;

圖5:

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

單元測試的代碼有一點變化,從構造函數傳入的IServiceReport接口已經被Mock過了,其實這是單元測試架構的一中,.NET本身提供的Fakes架構也是很不錯的,會給出所有背景的自動生成的模拟代碼,而且跟VisualStudioIDE是結合的,很不錯;

這個時候我們就可以控制IServiceReport接口的任何行為,我們隻有将實作換成接口才能使Mock有機會插入邏輯;

按照這樣的單元測試用例,那麼用例代碼是過不去的,因為我傳回了一個null類型的Report對象,這裡你就完全可以控制它人會的任何值,是以你的單元測試類不會受到任何外界的幹擾,進而使得你的代碼具有可測試性;

到目前為止文章的中心已經講到,我們也看到一個簡單的示例,如何從面向接口程式設計中找到理由這麼設計,其實也就是說面向接口程式設計就會使得類具有可測試性;單元測試與重構是一直持續下去的過程,代碼每天都有人在維護,每天都有人在使用單元測試用例,它們之間形成了一個良好的疊代關系;

圖6:

.NET項目開發—淺談面向接口程式設計、可測試性、單元測試、疊代重構(項目小結)1】開篇介紹2】疊代測試、重構(強制性面向接口程式設計,要求代碼具有可測試性)

這樣持續下去代碼始終保持一個很穩定的狀态,重構過後的代碼通過單元測試進行驗證,新加入的功能也可以使用單元測試進行實時驗證;

LINQ我們用的還是蠻多的,它對于集合的處理是相當不錯的,寫起來很順手,思維也比較連貫;但是LINQ對于單元測試來說需要在編寫的時候要注意,不能過于太長,如果太長很難進行測試,就是代碼覆寫到了也很難做到100%覆寫率,是以如果我們有兩個嵌套以上的建議還是分成兩個獨立的方法,這樣代碼就很容易測試了,就算以後改到了也不怕會影響其他的邏輯;

一個很好的建議就是将LINQ的表達式通過方法來傳回,方法裡面就好比是規約一樣的工廠,将具體的LINQ表達式放入一個統一的地方管理;

總結:其實我對單元測試、重構也隻是一點了解而已,隻不過最近對它的了解深入了一點,是以寫出來算是對項目的一個總結,覺得還是有很大的參考價值的;任何一個新東西,在我們沒有去學習研究它的時候覺得很一般,其實真正去研究了學習了會發現真的很讓人吃驚,任何一個東西都會有存在的價值,就看我們是否需要用;很多項目包括我之前的公司長期再維護一個已經無法再維護的項目,就是因為缺乏重構、測試是以變成今天的局面,用我們公司上司的一句話說,将變成公司的“技術債務”,遲早是需要換的;其實慢慢的也就變成了公司的一個巨大的資源消耗點、累贅;

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

繼續閱讀