閱讀目錄:
創(chuàng)新互聯(lián)長期為上1000家客戶提供的網(wǎng)站建設(shè)服務(wù),團隊從業(yè)經(jīng)驗10年,關(guān)注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為福海企業(yè)提供專業(yè)的網(wǎng)站制作、網(wǎng)站建設(shè),福海網(wǎng)站改版等技術(shù)服務(wù)。擁有10余年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。
1.開篇介紹
2.迭代測試、重構(gòu)(強制性面向接口編程,要求代碼具有可測試性)
2.1.面向接口編程的兩個設(shè)計誤區(qū)
2.1.1.接口的依賴倒置
2.1.2.接口對實體的抽象
2.2.迭代單元測試、重構(gòu)(代碼可測試)
2.2.1.LINQ表達式對單元測試的影響
最近一段時間結(jié)束了一個小項目的開發(fā),覺得有些好東西值得總結(jié)與分享,所以花點時間整理成文章;
大多數(shù)情況下我們都知道這些概念,面向接口編程是老生常談的話題了,有幾年編程經(jīng)驗的都知道怎么運用;單元測試其實在前幾年不怎么被重視,然而最近逐漸的浮現(xiàn)在我們眼前,而且被提起的頻率也大了很多了,包括重構(gòu)、可測試性都慢慢的貼近我們,我們只有親自動手去使用它才能領(lǐng)悟其精髓;
下面我將總結(jié)一下我對上述幾個概念之間的新體會;
面向接口編程要求我們彼此之間使用接口的方式調(diào)用,將一切可能存在變化的實例隔離在內(nèi)部,這些實例都只是一個可以隨時被替換的幕后勞動者;但是面向接口編程是需要一定的設(shè)計能力,能否合理的將對象抽象出接口來,真是一句兩句話無法概括的;
面向接口設(shè)計其實本人覺得會有一些細節(jié)的設(shè)計誤區(qū),既然抽象出接口那么就存在接口依賴的問題,還有就是對于Entity類型的抽象是否合理,是否會打亂Entity的清晰度,因為我們對DomainModel的理解是DomainEntity是一個POCO的對象,就是一個很簡單的純凈的類實體,一目了然,如果換成接口對后面的DDD的開發(fā)會有很大的麻煩,因為對接口的支持無法做到簡單的持久化,還有就是思維上的轉(zhuǎn)變也有很大的麻煩;
首先我覺得第一個誤區(qū)就是接口的依賴問題,接口的依賴不是一個小問題,在真實的項目中層之間的依賴是有嚴格的要求的,傳統(tǒng)分層架構(gòu)要求上層只能夠依賴下層,而DDD分層架構(gòu)是DomaiModel層絕對的無任何依賴,DomainModel不會去引用下層的基礎(chǔ)設(shè)施,因為它要求絕對的干凈;但是發(fā)現(xiàn)還是有很多的項目沒有能夠理解DDD的這點優(yōu)點;然后就是對于層之間的實體抽取接口,其實這點真的有待商量,DataAccess Layer中的數(shù)據(jù)實體嚴格意義說是DTO對象是用來過度到Business Layer中使用的,那么如果將DataAccess中的DTO設(shè)計成接口類型對外提供使用,Business Layer 就依賴上了DataAccess Layer了,所以還是需要根據(jù)項目的具體需求來平衡,下面我們看一下示例及分析;
傳統(tǒng)的三層架構(gòu),在Facade中調(diào)用BLL的方法,BLL調(diào)用DAL方法,這難道不是違背了“單一職責”原則嗎;一直我們都在強調(diào)“單一職責”設(shè)計原則,為什么很多項目的每層之間都是直接使用下層的接口,特別是我們的核心DomainModel層中,本來就是很干凈的純業(yè)務(wù)處理,來一個什么數(shù)據(jù)訪問的接口真的很不美;
圖1:

這種架構(gòu)應(yīng)該是大部分的項目的結(jié)構(gòu),我們應(yīng)該一眼就看出問題在哪里了,很明顯在Bl Layer中直接使用了Da Layer 相關(guān)接口獲取數(shù)據(jù),單純從這一點就有點違背單一職責設(shè)計原則;
圖2:

接口依賴倒置到底是誰向誰倒置了,第一張圖是業(yè)務(wù)層依賴了數(shù)據(jù)層,詳細點就是依賴了數(shù)據(jù)訪問的接口;第二張圖中業(yè)務(wù)層沒有依賴任何東西,細心的朋友應(yīng)該看到第二張圖中多了一個“DomainModel Event route ” 的東西,這是一種機制,目的是讓領(lǐng)域內(nèi)部產(chǎn)生領(lǐng)域事件,類似事件路由的效果,基礎(chǔ)設(shè)施要做任何的事情跟DomaiModel Entity 本身沒有任何關(guān)系;
實體的抽象如果變成接口會很別扭,我們對實體的最直觀的認識是一個很POCO的對象,但是如果你在設(shè)計的時候?qū)?shù)據(jù)訪問的DTO都設(shè)計成接口是否是有點不必要,有兩個情況下可以平衡這種需要,第一如果你的DTO不需要業(yè)務(wù)層傳入數(shù)據(jù)層那么無所謂的,那么如果是需要業(yè)務(wù)層傳入數(shù)據(jù)層的接口肯定是不行的,這里就是覺得將實體與接口的概念扯到一起很不直觀,像業(yè)務(wù)實體你把它抽層接口對持久化來說就是一個問題了;
其實這篇文章的主要內(nèi)容是在這一節(jié),上一節(jié)我說了一下我對接口抽象的一點個人看法;這一節(jié)我們將通過一個具體的示例來看一下這篇文章的重要內(nèi)容,看看單元測試如何與持續(xù)迭代重構(gòu)完美結(jié)合的,在編寫單元測試用例的時候我們將發(fā)現(xiàn)代碼被逐漸的重構(gòu)的很優(yōu)美,面向接口編程再一次被提到一個高度;
在我們編寫代碼的時候一般情況下無法驗證我們的代碼好與壞,光憑嘴說也很難斷定每個人的設(shè)計思路是否完全正確的,所以代碼可測試性將成為驗證你所編寫的代碼的質(zhì)量的一個重要指標;
單元測試與重構(gòu)將是一個持續(xù)迭代的過程,很多人并不太關(guān)心重構(gòu)和單元測試,其實是因為我們大部分情況下在開發(fā)一次性的交付的項目而不是持續(xù)更新的產(chǎn)品,所以單元測試、重構(gòu)被我們所忽視,面向接口編程也被我們時而記起也時而忘記,下面我們來看一下如何編寫可測試性的代碼;
/*==============================================================================
* Author:深度訓(xùn)練
* Create time: 2013-08-24
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定領(lǐng)域軟件工程實踐;
*==============================================================================*/
namespace UnittestDemo
{
using System.Linq.Expressions;
using System;
public static class ServiceReport
{
public static Report QueryReport(string queryWhere)
{
return new Report();
}
}
}這是一個很簡單的靜態(tài)類,主要目的是模擬根據(jù)查詢條件從服務(wù)器上查詢相關(guān)的報表信息,由于這里是為了演示所以直接返回了Report對象,只是作為實例演示,Report是作為報表對象的抽象,沒有任何的數(shù)據(jù)字段;
/*==============================================================================
* Author:深度訓(xùn)練
* Create time: 2013-08-24
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定領(lǐng)域軟件工程實踐;
*==============================================================================*/
namespace UnittestDemo
{
using System;
public class ReportAnalyse
{
public bool Analyse(DateTime dt)
{
ServiceReport.QueryReport(string.Format("State={0}", 1));
return true;
}
}
}這是一個實例類,用來對遠程返回的表達進行分析,就好比一個業(yè)務(wù)一個數(shù)據(jù)訪問,只不過這里的數(shù)據(jù)訪問大部分情況下我們都會使用靜態(tài)類來實現(xiàn);
/*==============================================================================
* Author:深度訓(xùn)練
* Create time: 2013-08-24
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定領(lǐng)域軟件工程實踐;
*==============================================================================*/
namespace UnittestDemo
{
using System;
public class AppStart
{
public static void MainStart()
{
ReportAnalyse analyse = new ReportAnalyse();
bool result = analyse.Analyse(DateTime.Now);
if (result)
{
//
}
else
{
//
}
}
}
}這個就是程序調(diào)用的地方,用來模擬程序運行時的入口,可以當成是Application Layer中的Facade對象;
其實這里就能看出來我在2.1】小結(jié)中說的“單一職責”設(shè)計原則,我已經(jīng)將數(shù)據(jù)訪問代碼在ReportAnalyse中使用了,其實這里是不對的,應(yīng)該是在外部裝載好然后傳入ReportAnalyse中才對,才符合單一職責設(shè)計原則,當然這里不是講它,所以不扯了;
我們假設(shè)上面的代碼已經(jīng)完成了對Report對象的分析了,下面我們需要對代碼進行單元測試,主要是兩個類ReportAnalyse、ServiceReport,我們先從ReportAnalyse類開始吧;
【單元測試】
創(chuàng)建基本的單元測試項目,然后記得引用被測試項目,最后新建一個用來測試ReportAnalyse類的單元測試文件;
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);
}
}
}寫上很簡單的測試用例,這里的主要目的不是怎么寫測試用例,也不是怎么測試代碼,這里的目的是如何進行單元測試、重構(gòu)等迭代的過程,所以如何寫用例不是重點,這里直接帶過了;
圖3:

如果沒有問題的話,這個單元測試用例肯定是過的,因為沒有其他什么邏輯,很簡單的兩行代碼;看起來一起很好,沒有問題,單元測試也通過了,這個時候我們放心的去做其他的功能了,但是過了幾天發(fā)現(xiàn)自己的ReportAnalyse單元測試突然不過了,后來檢查發(fā)現(xiàn)有人改了ServiceReport實現(xiàn),原本從本地直接實例化的Report現(xiàn)在需要配置過后才能使用,也就是說你這個時候測試不了你的代碼了,以為你的ReportAnalyse會隨時受到ServiceReport的影響,但是這個問題如果在運行時是無所謂的,畢竟在產(chǎn)線上都是配置好的;
這個時候就會是牽一發(fā)而動全身的困境,因為我們的代碼是面向?qū)崿F(xiàn)編程的,也就是說耦合度很高,這個時候我們需要根據(jù)需要對ServiceReport進行適當?shù)闹貥?gòu),當然重構(gòu)的首要目標就是將它與任何實現(xiàn)脫耦;
下面我們將ServiceReport提取出一個接口,然后通過IOC的方式動態(tài)的注入進來就實現(xiàn)了完全的脫耦;
/*==============================================================================
* Author:深度訓(xùn)練
* Create time: 2013-08-24
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定領(lǐng)域軟件工程實踐;
*==============================================================================*/
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ù)當然不是直接實例化的,需要使用相關(guān)的IOC框架做支撐;我們看一下上面的代碼很簡潔,依賴IServiceReport接口,這個時候我們再回過頭來對單元測試進行簡單的修改來適應(yīng)可以持續(xù)重構(gòu)的代碼;
為了使代碼好測試點,我修改了一下Analyse方法;
圖4:

畫紅線的部分在我們沒有進行重構(gòu)之前是會隨著ServiceReport的變化而變化的,但是被我們抽象成接口之后就變的很容易測試了,我們自己可以任何控制它的返回值;
圖5:

單元測試的代碼有一點變化,從構(gòu)造函數(shù)傳入的IServiceReport接口已經(jīng)被Mock過了,其實這是單元測試框架的一中,.NET本身提供的Fakes框架也是很不錯的,會給出所有后臺的自動生成的模擬代碼,而且跟VisualStudioIDE是結(jié)合的,很不錯;
這個時候我們就可以控制IServiceReport接口的任何行為,我們只有將實現(xiàn)換成接口才能使Mock有機會插入邏輯;
按照這樣的單元測試用例,那么用例代碼是過不去的,因為我返回了一個null類型的Report對象,這里你就完全可以控制它人會的任何值,所以你的單元測試類不會受到任何外界的干擾,從而使得你的代碼具有可測試性;
到目前為止文章的中心已經(jīng)講到,我們也看到一個簡單的示例,如何從面向接口編程中找到理由這么設(shè)計,其實也就是說面向接口編程就會使得類具有可測試性;單元測試與重構(gòu)是一直持續(xù)下去的過程,代碼每天都有人在維護,每天都有人在使用單元測試用例,它們之間形成了一個良好的迭代關(guān)系;
圖6:

這樣持續(xù)下去代碼始終保持一個很穩(wěn)定的狀態(tài),重構(gòu)過后的代碼通過單元測試進行驗證,新加入的功能也可以使用單元測試進行實時驗證;
LINQ我們用的還是蠻多的,它對于集合的處理是相當不錯的,寫起來很順手,思維也比較連貫;但是LINQ對于單元測試來說需要在編寫的時候要注意,不能過于太長,如果太長很難進行測試,就是代碼覆蓋到了也很難做到100%覆蓋率,所以如果我們有兩個嵌套以上的建議還是分成兩個獨立的方法,這樣代碼就很容易測試了,就算以后改到了也不怕會影響其他的邏輯;
一個很好的建議就是將LINQ的表達式通過方法來返回,方法里面就好比是規(guī)約一樣的工廠,將具體的LINQ表達式放入一個統(tǒng)一的地方管理;
總結(jié):其實我對單元測試、重構(gòu)也只是一點了解而已,只不過最近對它的理解深入了一點,所以寫出來算是對項目的一個總結(jié),覺得還是有很大的參考價值的;任何一個新東西,在我們沒有去學習研究它的時候覺得很一般,其實真正去研究了學習了會發(fā)現(xiàn)真的很讓人吃驚,任何一個東西都會有存在的價值,就看我們是否需要用;很多項目包括我之前的公司長期再維護一個已經(jīng)無法再維護的項目,就是因為缺乏重構(gòu)、測試所以變成今天的局面,用我們公司領(lǐng)導(dǎo)的一句話說,將變成公司的“技術(shù)債務(wù)”,遲早是需要換的;其實慢慢的也就變成了公司的一個巨大的資源消耗點、累贅;
示例代碼地址:http://files.cnblogs.com/wangiqngpei557/UnittestDemo.zip
作者:王清培
出處:http://wangqingpei557.blog.51cto.com/
本文版權(quán)歸作者和51CTO共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權(quán)利。
當前名稱:.NET項目開發(fā)—淺談面向接口編程、可測試性、單元測試、迭代重構(gòu)(項目小結(jié))
當前網(wǎng)址:http://chinadenli.net/article20/gdgpco.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營銷推廣、電子商務(wù)、Google、商城網(wǎng)站、域名注冊、品牌網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)