閱讀目錄:
創(chuàng)新互聯(lián)長(zhǎng)期為上1000家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為福海企業(yè)提供專業(yè)的網(wǎng)站制作、網(wǎng)站建設(shè),福海網(wǎng)站改版等技術(shù)服務(wù)。擁有10余年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。
1.開篇介紹
2.迭代測(cè)試、重構(gòu)(強(qiáng)制性面向接口編程,要求代碼具有可測(cè)試性)
2.1.面向接口編程的兩個(gè)設(shè)計(jì)誤區(qū)
2.1.1.接口的依賴倒置
2.1.2.接口對(duì)實(shí)體的抽象
2.2.迭代單元測(cè)試、重構(gòu)(代碼可測(cè)試)
2.2.1.LINQ表達(dá)式對(duì)單元測(cè)試的影響
最近一段時(shí)間結(jié)束了一個(gè)小項(xiàng)目的開發(fā),覺得有些好東西值得總結(jié)與分享,所以花點(diǎn)時(shí)間整理成文章;
大多數(shù)情況下我們都知道這些概念,面向接口編程是老生常談的話題了,有幾年編程經(jīng)驗(yàn)的都知道怎么運(yùn)用;單元測(cè)試其實(shí)在前幾年不怎么被重視,然而最近逐漸的浮現(xiàn)在我們眼前,而且被提起的頻率也大了很多了,包括重構(gòu)、可測(cè)試性都慢慢的貼近我們,我們只有親自動(dòng)手去使用它才能領(lǐng)悟其精髓;
下面我將總結(jié)一下我對(duì)上述幾個(gè)概念之間的新體會(huì);
面向接口編程要求我們彼此之間使用接口的方式調(diào)用,將一切可能存在變化的實(shí)例隔離在內(nèi)部,這些實(shí)例都只是一個(gè)可以隨時(shí)被替換的幕后勞動(dòng)者;但是面向接口編程是需要一定的設(shè)計(jì)能力,能否合理的將對(duì)象抽象出接口來,真是一句兩句話無法概括的;
面向接口設(shè)計(jì)其實(shí)本人覺得會(huì)有一些細(xì)節(jié)的設(shè)計(jì)誤區(qū),既然抽象出接口那么就存在接口依賴的問題,還有就是對(duì)于Entity類型的抽象是否合理,是否會(huì)打亂Entity的清晰度,因?yàn)槲覀儗?duì)DomainModel的理解是DomainEntity是一個(gè)POCO的對(duì)象,就是一個(gè)很簡(jiǎn)單的純凈的類實(shí)體,一目了然,如果換成接口對(duì)后面的DDD的開發(fā)會(huì)有很大的麻煩,因?yàn)閷?duì)接口的支持無法做到簡(jiǎn)單的持久化,還有就是思維上的轉(zhuǎn)變也有很大的麻煩;
首先我覺得第一個(gè)誤區(qū)就是接口的依賴問題,接口的依賴不是一個(gè)小問題,在真實(shí)的項(xiàng)目中層之間的依賴是有嚴(yán)格的要求的,傳統(tǒng)分層架構(gòu)要求上層只能夠依賴下層,而DDD分層架構(gòu)是DomaiModel層絕對(duì)的無任何依賴,DomainModel不會(huì)去引用下層的基礎(chǔ)設(shè)施,因?yàn)樗蠼^對(duì)的干凈;但是發(fā)現(xiàn)還是有很多的項(xiàng)目沒有能夠理解DDD的這點(diǎn)優(yōu)點(diǎn);然后就是對(duì)于層之間的實(shí)體抽取接口,其實(shí)這點(diǎn)真的有待商量,DataAccess Layer中的數(shù)據(jù)實(shí)體嚴(yán)格意義說是DTO對(duì)象是用來過度到Business Layer中使用的,那么如果將DataAccess中的DTO設(shè)計(jì)成接口類型對(duì)外提供使用,Business Layer 就依賴上了DataAccess Layer了,所以還是需要根據(jù)項(xiàng)目的具體需求來平衡,下面我們看一下示例及分析;
傳統(tǒng)的三層架構(gòu),在Facade中調(diào)用BLL的方法,BLL調(diào)用DAL方法,這難道不是違背了“單一職責(zé)”原則嗎;一直我們都在強(qiáng)調(diào)“單一職責(zé)”設(shè)計(jì)原則,為什么很多項(xiàng)目的每層之間都是直接使用下層的接口,特別是我們的核心DomainModel層中,本來就是很干凈的純業(yè)務(wù)處理,來一個(gè)什么數(shù)據(jù)訪問的接口真的很不美;
圖1:
這種架構(gòu)應(yīng)該是大部分的項(xiàng)目的結(jié)構(gòu),我們應(yīng)該一眼就看出問題在哪里了,很明顯在Bl Layer中直接使用了Da Layer 相關(guān)接口獲取數(shù)據(jù),單純從這一點(diǎn)就有點(diǎn)違背單一職責(zé)設(shè)計(jì)原則;
圖2:
接口依賴倒置到底是誰向誰倒置了,第一張圖是業(yè)務(wù)層依賴了數(shù)據(jù)層,詳細(xì)點(diǎn)就是依賴了數(shù)據(jù)訪問的接口;第二張圖中業(yè)務(wù)層沒有依賴任何東西,細(xì)心的朋友應(yīng)該看到第二張圖中多了一個(gè)“DomainModel Event route ” 的東西,這是一種機(jī)制,目的是讓領(lǐng)域內(nèi)部產(chǎn)生領(lǐng)域事件,類似事件路由的效果,基礎(chǔ)設(shè)施要做任何的事情跟DomaiModel Entity 本身沒有任何關(guān)系;
實(shí)體的抽象如果變成接口會(huì)很別扭,我們對(duì)實(shí)體的最直觀的認(rèn)識(shí)是一個(gè)很POCO的對(duì)象,但是如果你在設(shè)計(jì)的時(shí)候?qū)?shù)據(jù)訪問的DTO都設(shè)計(jì)成接口是否是有點(diǎn)不必要,有兩個(gè)情況下可以平衡這種需要,第一如果你的DTO不需要業(yè)務(wù)層傳入數(shù)據(jù)層那么無所謂的,那么如果是需要業(yè)務(wù)層傳入數(shù)據(jù)層的接口肯定是不行的,這里就是覺得將實(shí)體與接口的概念扯到一起很不直觀,像業(yè)務(wù)實(shí)體你把它抽層接口對(duì)持久化來說就是一個(gè)問題了;
其實(shí)這篇文章的主要內(nèi)容是在這一節(jié),上一節(jié)我說了一下我對(duì)接口抽象的一點(diǎn)個(gè)人看法;這一節(jié)我們將通過一個(gè)具體的示例來看一下這篇文章的重要內(nèi)容,看看單元測(cè)試如何與持續(xù)迭代重構(gòu)完美結(jié)合的,在編寫單元測(cè)試用例的時(shí)候我們將發(fā)現(xiàn)代碼被逐漸的重構(gòu)的很優(yōu)美,面向接口編程再一次被提到一個(gè)高度;
在我們編寫代碼的時(shí)候一般情況下無法驗(yàn)證我們的代碼好與壞,光憑嘴說也很難斷定每個(gè)人的設(shè)計(jì)思路是否完全正確的,所以代碼可測(cè)試性將成為驗(yàn)證你所編寫的代碼的質(zhì)量的一個(gè)重要指標(biāo);
單元測(cè)試與重構(gòu)將是一個(gè)持續(xù)迭代的過程,很多人并不太關(guān)心重構(gòu)和單元測(cè)試,其實(shí)是因?yàn)槲覀兇蟛糠智闆r下在開發(fā)一次性的交付的項(xiàng)目而不是持續(xù)更新的產(chǎn)品,所以單元測(cè)試、重構(gòu)被我們所忽視,面向接口編程也被我們時(shí)而記起也時(shí)而忘記,下面我們來看一下如何編寫可測(cè)試性的代碼;
/*============================================================================== * Author:深度訓(xùn)練 * Create time: 2013-08-24 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領(lǐng)域軟件工程實(shí)踐; *==============================================================================*/ namespace UnittestDemo { using System.Linq.Expressions; using System; public static class ServiceReport { public static Report QueryReport(string queryWhere) { return new Report(); } } }
這是一個(gè)很簡(jiǎn)單的靜態(tài)類,主要目的是模擬根據(jù)查詢條件從服務(wù)器上查詢相關(guān)的報(bào)表信息,由于這里是為了演示所以直接返回了Report對(duì)象,只是作為實(shí)例演示,Report是作為報(bào)表對(duì)象的抽象,沒有任何的數(shù)據(jù)字段;
/*============================================================================== * Author:深度訓(xùn)練 * Create time: 2013-08-24 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領(lǐng)域軟件工程實(shí)踐; *==============================================================================*/ namespace UnittestDemo { using System; public class ReportAnalyse { public bool Analyse(DateTime dt) { ServiceReport.QueryReport(string.Format("State={0}", 1)); return true; } } }
這是一個(gè)實(shí)例類,用來對(duì)遠(yuǎn)程返回的表達(dá)進(jìn)行分析,就好比一個(gè)業(yè)務(wù)一個(gè)數(shù)據(jù)訪問,只不過這里的數(shù)據(jù)訪問大部分情況下我們都會(huì)使用靜態(tài)類來實(shí)現(xiàn);
/*============================================================================== * Author:深度訓(xùn)練 * Create time: 2013-08-24 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領(lǐng)域軟件工程實(shí)踐; *==============================================================================*/ namespace UnittestDemo { using System; public class AppStart { public static void MainStart() { ReportAnalyse analyse = new ReportAnalyse(); bool result = analyse.Analyse(DateTime.Now); if (result) { // } else { // } } } }
這個(gè)就是程序調(diào)用的地方,用來模擬程序運(yùn)行時(shí)的入口,可以當(dāng)成是Application Layer中的Facade對(duì)象;
其實(shí)這里就能看出來我在2.1】小結(jié)中說的“單一職責(zé)”設(shè)計(jì)原則,我已經(jīng)將數(shù)據(jù)訪問代碼在ReportAnalyse中使用了,其實(shí)這里是不對(duì)的,應(yīng)該是在外部裝載好然后傳入ReportAnalyse中才對(duì),才符合單一職責(zé)設(shè)計(jì)原則,當(dāng)然這里不是講它,所以不扯了;
我們假設(shè)上面的代碼已經(jīng)完成了對(duì)Report對(duì)象的分析了,下面我們需要對(duì)代碼進(jìn)行單元測(cè)試,主要是兩個(gè)類ReportAnalyse、ServiceReport,我們先從ReportAnalyse類開始吧;
【單元測(cè)試】
創(chuàng)建基本的單元測(cè)試項(xiàng)目,然后記得引用被測(cè)試項(xiàng)目,最后新建一個(gè)用來測(cè)試ReportAnalyse類的單元測(cè)試文件;
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using UnittestDemo; namespace UnittestDemoUnit { [TestClass] public class ReportAnalyseUnitTest { [TestMethod] public void ReportAnalyse_Analyse_UnitTest() { ReportAnalyse testReportAnalyse = new ReportAnalyse(); bool result = testReportAnalyse.Analyse(DateTime.Now); Assert.IsTrue(result); } } }
寫上很簡(jiǎn)單的測(cè)試用例,這里的主要目的不是怎么寫測(cè)試用例,也不是怎么測(cè)試代碼,這里的目的是如何進(jìn)行單元測(cè)試、重構(gòu)等迭代的過程,所以如何寫用例不是重點(diǎn),這里直接帶過了;
圖3:
如果沒有問題的話,這個(gè)單元測(cè)試用例肯定是過的,因?yàn)闆]有其他什么邏輯,很簡(jiǎn)單的兩行代碼;看起來一起很好,沒有問題,單元測(cè)試也通過了,這個(gè)時(shí)候我們放心的去做其他的功能了,但是過了幾天發(fā)現(xiàn)自己的ReportAnalyse單元測(cè)試突然不過了,后來檢查發(fā)現(xiàn)有人改了ServiceReport實(shí)現(xiàn),原本從本地直接實(shí)例化的Report現(xiàn)在需要配置過后才能使用,也就是說你這個(gè)時(shí)候測(cè)試不了你的代碼了,以為你的ReportAnalyse會(huì)隨時(shí)受到ServiceReport的影響,但是這個(gè)問題如果在運(yùn)行時(shí)是無所謂的,畢竟在產(chǎn)線上都是配置好的;
這個(gè)時(shí)候就會(huì)是牽一發(fā)而動(dòng)全身的困境,因?yàn)槲覀兊拇a是面向?qū)崿F(xiàn)編程的,也就是說耦合度很高,這個(gè)時(shí)候我們需要根據(jù)需要對(duì)ServiceReport進(jìn)行適當(dāng)?shù)闹貥?gòu),當(dāng)然重構(gòu)的首要目標(biāo)就是將它與任何實(shí)現(xiàn)脫耦;
下面我們將ServiceReport提取出一個(gè)接口,然后通過IOC的方式動(dòng)態(tài)的注入進(jìn)來就實(shí)現(xiàn)了完全的脫耦;
/*============================================================================== * Author:深度訓(xùn)練 * Create time: 2013-08-24 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領(lǐng)域軟件工程實(shí)踐; *==============================================================================*/ namespace UnittestDemo { using System; public class ReportAnalyse { IServiceReport serviceReport; public ReportAnalyse(IServiceReport serviceReport) { this.serviceReport = serviceReport; } public bool Analyse(DateTime dt) { serviceReport.QueryReport(string.Format("State={0}", 1)); return true; } } }
這里的構(gòu)造函數(shù)當(dāng)然不是直接實(shí)例化的,需要使用相關(guān)的IOC框架做支撐;我們看一下上面的代碼很簡(jiǎn)潔,依賴IServiceReport接口,這個(gè)時(shí)候我們?cè)倩剡^頭來對(duì)單元測(cè)試進(jìn)行簡(jiǎn)單的修改來適應(yīng)可以持續(xù)重構(gòu)的代碼;
為了使代碼好測(cè)試點(diǎn),我修改了一下Analyse方法;
圖4:
畫紅線的部分在我們沒有進(jìn)行重構(gòu)之前是會(huì)隨著ServiceReport的變化而變化的,但是被我們抽象成接口之后就變的很容易測(cè)試了,我們自己可以任何控制它的返回值;
圖5:
單元測(cè)試的代碼有一點(diǎn)變化,從構(gòu)造函數(shù)傳入的IServiceReport接口已經(jīng)被Mock過了,其實(shí)這是單元測(cè)試框架的一中,.NET本身提供的Fakes框架也是很不錯(cuò)的,會(huì)給出所有后臺(tái)的自動(dòng)生成的模擬代碼,而且跟VisualStudioIDE是結(jié)合的,很不錯(cuò);
這個(gè)時(shí)候我們就可以控制IServiceReport接口的任何行為,我們只有將實(shí)現(xiàn)換成接口才能使Mock有機(jī)會(huì)插入邏輯;
按照這樣的單元測(cè)試用例,那么用例代碼是過不去的,因?yàn)槲曳祷亓艘粋€(gè)null類型的Report對(duì)象,這里你就完全可以控制它人會(huì)的任何值,所以你的單元測(cè)試類不會(huì)受到任何外界的干擾,從而使得你的代碼具有可測(cè)試性;
到目前為止文章的中心已經(jīng)講到,我們也看到一個(gè)簡(jiǎn)單的示例,如何從面向接口編程中找到理由這么設(shè)計(jì),其實(shí)也就是說面向接口編程就會(huì)使得類具有可測(cè)試性;單元測(cè)試與重構(gòu)是一直持續(xù)下去的過程,代碼每天都有人在維護(hù),每天都有人在使用單元測(cè)試用例,它們之間形成了一個(gè)良好的迭代關(guān)系;
圖6:
這樣持續(xù)下去代碼始終保持一個(gè)很穩(wěn)定的狀態(tài),重構(gòu)過后的代碼通過單元測(cè)試進(jìn)行驗(yàn)證,新加入的功能也可以使用單元測(cè)試進(jìn)行實(shí)時(shí)驗(yàn)證;
LINQ我們用的還是蠻多的,它對(duì)于集合的處理是相當(dāng)不錯(cuò)的,寫起來很順手,思維也比較連貫;但是LINQ對(duì)于單元測(cè)試來說需要在編寫的時(shí)候要注意,不能過于太長(zhǎng),如果太長(zhǎng)很難進(jìn)行測(cè)試,就是代碼覆蓋到了也很難做到100%覆蓋率,所以如果我們有兩個(gè)嵌套以上的建議還是分成兩個(gè)獨(dú)立的方法,這樣代碼就很容易測(cè)試了,就算以后改到了也不怕會(huì)影響其他的邏輯;
一個(gè)很好的建議就是將LINQ的表達(dá)式通過方法來返回,方法里面就好比是規(guī)約一樣的工廠,將具體的LINQ表達(dá)式放入一個(gè)統(tǒng)一的地方管理;
總結(jié):其實(shí)我對(duì)單元測(cè)試、重構(gòu)也只是一點(diǎn)了解而已,只不過最近對(duì)它的理解深入了一點(diǎn),所以寫出來算是對(duì)項(xiàng)目的一個(gè)總結(jié),覺得還是有很大的參考價(jià)值的;任何一個(gè)新東西,在我們沒有去學(xué)習(xí)研究它的時(shí)候覺得很一般,其實(shí)真正去研究了學(xué)習(xí)了會(huì)發(fā)現(xiàn)真的很讓人吃驚,任何一個(gè)東西都會(huì)有存在的價(jià)值,就看我們是否需要用;很多項(xiàng)目包括我之前的公司長(zhǎng)期再維護(hù)一個(gè)已經(jīng)無法再維護(hù)的項(xiàng)目,就是因?yàn)槿狈χ貥?gòu)、測(cè)試所以變成今天的局面,用我們公司領(lǐng)導(dǎo)的一句話說,將變成公司的“技術(shù)債務(wù)”,遲早是需要換的;其實(shí)慢慢的也就變成了公司的一個(gè)巨大的資源消耗點(diǎn)、累贅;
示例代碼地址:http://files.cnblogs.com/wangiqngpei557/UnittestDemo.zip
作者:王清培
出處:http://wangqingpei557.blog.51cto.com/
本文版權(quán)歸作者和51CTO共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
當(dāng)前名稱:.NET項(xiàng)目開發(fā)—淺談面向接口編程、可測(cè)試性、單元測(cè)試、迭代重構(gòu)(項(xiàng)目小結(jié))
當(dāng)前網(wǎng)址:http://vcdvsql.cn/article20/gdgpco.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營(yíng)銷推廣、電子商務(wù)、Google、商城網(wǎng)站、域名注冊(cè)、品牌網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)