天天躁日日躁狠狠躁AV麻豆-天天躁人人躁人人躁狂躁-天天澡夜夜澡人人澡-天天影视香色欲综合网-国产成人女人在线视频观看-国产成人女人视频在线观看

盡可能擺脫對HttpContext的依賴

  我們繼續(xù)《ASP.NET MVC單元測試最佳實踐》,今天主要談?wù)揌ttpContext的依賴問題。

  在ASP.NET中進行單元測試的天敵便是HttpContext,它是ASP.NET的核心,極端復(fù)雜,卻無法進行Mock1——可見微軟能夠?qū)懗瞿敲待嫶蟮?a href=/itjie/ASPjishu/ target=_blank class=infotextkey>ASP.NET框架真不那么容易。現(xiàn)在這個狀況改善了不少,因此大家已經(jīng)可以使用System.Web.Abstractions.dll了,這個程序集中提供了對于HttpContext的抽象,也就是HttpContextBase抽象類。因此在ASP.NET MVC中,各種組件均依賴于HttpContextBase而不是HttpContext。這是一個優(yōu)秀的做法,大家以后可以盡可能地擺脫HttpContext了。

  不過這似乎又是一個悖論。雖然已經(jīng)可以對HttpContext進行Mock(這點增強了可測試性),但是過度依賴HttpContext對于單元測試來說也是一個傷害。這是HttpContext對象的天性所致:它實在太復(fù)雜了。您應(yīng)該已經(jīng)察覺到,這是個集萬千寵愛于一身的對象,從請求,回復(fù),應(yīng)用程序,緩存……幾乎包含了Web應(yīng)用程序需要的所有信息。如果要測試一個依賴于HttpContext的方法,您勢必要為HttpContext的Mock對象填充各種信息——其復(fù)雜程度視業(yè)務(wù)而定。而且,Mock關(guān)注的是“行為”,也就是說它關(guān)注的是做一件事情所使用“路徑”。那么如果做一件事情可以采用多個路徑又會怎樣?是否需要在測試之前準(zhǔn)備好所有的路徑,并且驗證被測試的代碼“采用了,并僅僅采用了其中一條路徑”?因此,Stub慢慢進入人們的視線。Stub關(guān)注的是“狀態(tài)”……這就是另一個話題了,還會涉及到采用Record & Replay還是Arrange-Act-Assert方式來進行單元測試,暫且不提。

  之前談到對視圖進行單元測試時,老趙曾經(jīng)談起在視圖中應(yīng)該只使用ViewData中的數(shù)據(jù)。這不是第一次說起要放棄HttpContext了,自從有了“抽象”這一有利武器后,一切“不和諧”因素都能夠被分離。試想在MVP模式中,View和Presenter都使用各自的抽象進行交互,一切Web控件,HttpContext等對象都不復(fù)存在了,大家眼中只有“數(shù)據(jù)”和“模型”。同樣,在ASP.NET MVC的Action方法中,也不應(yīng)該使用HttpContext,這是基于良好的“可測試性”而考慮的。您可能會想,現(xiàn)在的HttpContextBase對象已經(jīng)可以Mock了啊。沒錯,它的確“可以”,但是這樣做會引起單元測試代碼的膨脹,因為測試代碼中的相當(dāng)部分必須關(guān)注在測試數(shù)據(jù)的準(zhǔn)備,而不是被測試的功能上。對于一個Action方法來說,它關(guān)注的應(yīng)該是用戶與業(yè)務(wù)邏輯的交互,而不是“如何把HTTP請求轉(zhuǎn)化為可用的數(shù)據(jù)”。其實說到底,還是要“分離關(guān)注點”。

  在ASP.NET MVC中負責(zé)“轉(zhuǎn)化數(shù)據(jù)”的層次為Model Binder。關(guān)于這一點,現(xiàn)有的“示例”大都關(guān)注把Form或QueryString中的數(shù)據(jù)轉(zhuǎn)化為Action參數(shù)上,不過Model Binder可用的地方其實更多。例如在《最佳實踐》的代碼中,原本AccountController的Delete方法實現(xiàn)如下:

public ActionResult Delete(string userName){    this.MiddleTier.UserManager.Delete(userName);    Uri urlReferrer = this.Request.UrlReferrer;    return this.Redirect(urlReferrer.ToString());}

  在刪除了指定對象之后,頁面將跳轉(zhuǎn)到Url Referrer地址中。在上面的代碼中,這個值將通過訪問Request.UrlReferer來獲得。這就使您的Action方法與HttpContext產(chǎn)生了依賴,因此它的單元測試代碼就需要這樣編寫:

[TestMethod]public void DeleteTest(){    string userName = "jeffz";    Uri urlReferrer = new Uri("http://www.microsoft.com");    var mockHttpContext = new Mock<HttpContextBase>();    mockHttpContext.Setup(c => c.Request.UrlReferrer).Returns(urlReferrer);    var mockController = this.GetMockController();    mockController.Setup(c => c.MiddleTier.UserManager.Delete(userName)).Verifiable();    mockController.Object.ControllerContext = new ControllerContext(        mockHttpContext.Object, new RouteData(), mockController.Object);    mockController.Object.Delete(userName)...}

  在單元測試代碼中,我們Mock了一個HttpContextBase對象,讓它的Request.UrlReferrer屬性返回我們準(zhǔn)備好的對象,再構(gòu)造一個新的ControllerContext并交給Controller。而如果我們的UrlReferrer能夠作為Delete方法的參數(shù),那么單元測試代碼就會一下子簡單很多:

[TestMethod()]public void DeleteTest(){    string userName = "jeffz";    Uri urlReferrer = new Uri("http://www.microsoft.com");    var mockController = this.GetMockController();    mockController.Setup(c => c.MiddleTier.UserManager.Delete(userName)).Verifiable();    mockController.Object.Delete(userName, urlReferrer)...}

  有些朋友可能會問,不就是從Request的UrlReferrer屬性中取值嗎?我們?yōu)槭裁匆獦?gòu)造一個ControllerContext,不能直接設(shè)置Controller對象嗎?例如這樣就簡單多了:

mockController.Setup(c => c.Request.UrlReferrer).Returns(urlReferrer);

  似乎可行,不過您運行的時候就會發(fā)現(xiàn),框架會拋出異常,說只有接口的成員,或可以override的成員才能夠被Mock。沒錯,Controller的Request屬性不是virtual的,無法override。Controller類如此設(shè)計是故意的,目的就是限制了可用的路徑。試想,如果您Mock了Controller.Request屬性,但是程序代碼通過Controller.HttpContext.Request進行訪問又怎么辦呢?類似的做法還有對方法重載的設(shè)計。一般來說,都會把其中幾個方法委托給其中唯一的方法,而只有那個方法是可以被override的。這樣在編寫測試時,我們僅有的Mock入口便確定了,避免了測試代碼過度了解方法實現(xiàn)的問題。

  回到正題。如果要讓Delete方法接urlReferrer受參數(shù),那么我們就要編寫Model Binder相關(guān)的組件:

public class UrlReferrerModelBinder : IModelBinder{    public object BindModel(        ControllerContext controllerContext,        ModelBindingContext bindingContext)    {        return controllerContext.HttpContext.Request.UrlReferrer;    }}

  并使其可以直接運用到Action的參數(shù)上:

public class UrlReferrerAttribute : CustomModelBinderAttribute{    private static UrlReferrerModelBinder s_modelBinder =        new UrlReferrerModelBinder();    public override IModelBinder GetBinder()    {        return s_modelBinder;    }}

  于是乎,我們的Delete方法便可寫為:

public ActionResult Delete(string userName, Uri urlReferrer){    this.MiddleTier.UserManager.Delete(userName);    return this.Redirect(urlReferrer.ToString());}

  如今的代碼,無論是應(yīng)用程序還是框架類庫,都必須考慮“可測試性”這個要求。例如.NET 3.0的WF,由于其可測試性不佳一直為人所詬病。現(xiàn)在我們在編寫程序時,要時刻詢問自己:“這么做方便測試嗎?”考慮到這個問題,可能您就會放心地做出某些抉擇了2

  注1:其實還是可以Mock的。例如Typemock使用Profiler的方式進行直接注入,可以Mock任何成員。不過,如果Moq等框架無法滿足您的需要,一般便是您的設(shè)計有些問題了。

  注2:例如,究竟讓Action方法返回ActionResult,還是返回void,并直接通過Response輸出呢?

 

NET技術(shù)盡可能擺脫對HttpContext的依賴,轉(zhuǎn)載需保留來源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 少妇内射视频播放舔大片 | 2019香蕉在线观看直播视频 | 亚洲精品国产SUV | 啊…嗯啊好深男男高h文总受 | 国产VA精品午夜福利视频 | 找老女人泻火对白自拍 | 2022久久精品国产色蜜蜜麻豆 | 天天躁夜夜踩很很踩2022 | xvideos中文版在线视频 | 国产亚洲精品AV麻豆狂野 | 亚洲天堂av2017 | ewp系列虐杀在线视频 | 97国产成人精品免费视频 | 国产精品亚洲视频在线观看 | 国产69精品久久久久无码麻豆 | 日本无码免费久久久精品 | 天天澡夜夜澡人人澡 | 国产午夜精品鲁丝片 | 男人就爱吃这套下载 | 扒开女人下面使劲桶视频 | 日日夜夜天天操 | 使劲别停好大好深好爽动态图 | 琪琪电影午夜理论片YY6080 | 视频一区精品自拍亚洲 | 久久精品黄色 | 国产精品亚洲国产三区 | 三级黄视频 | 欧美精品久久久久性色AV苍井 | 男人J桶进男人屁股过程 | 365电影成人亚洲网在线观看 | 最近的中文字幕2019国语 | 国产AV国产精品国产三级在线L | 挺弄抽插喷射HH | 看电影来5566一区.二区 | 在线色av | 快穿之诱受双性被灌满h | 99久久久A片无码国产精 | 国产精品久久久久一区二区三区 | 中文字幕无码亚洲字幕成A人蜜桃 | 考试考90就可以晚上和老师C | 亚洲乱码日产精品BD在线下载 |