日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

艾伟:尽可能摆脱对HttpContext的依赖

發(fā)布時(shí)間:2025/7/14 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 艾伟:尽可能摆脱对HttpContext的依赖 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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

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

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

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

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

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

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

[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)... }

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

[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)... }

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

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

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

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

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

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

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

  于是乎,我們的Delete方法便可寫(xiě)為:

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

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

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

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

?

總結(jié)

以上是生活随笔為你收集整理的艾伟:尽可能摆脱对HttpContext的依赖的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。