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

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

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > asp.net >内容正文

asp.net

使用ASP.NET Abstractions增强ASP.NET应用程序的可测试性

發(fā)布時(shí)間:2025/6/17 asp.net 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用ASP.NET Abstractions增强ASP.NET应用程序的可测试性 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

概述

在閱讀本文之前,兄弟們請(qǐng)先注意兩點(diǎn):

  • 我們現(xiàn)在談的是傳統(tǒng)ASP.NET應(yīng)用程序的可測(cè)試性,而不是ASP.NET MVC應(yīng)用程序的可測(cè)試性。
  • 我們現(xiàn)在談的是“增強(qiáng)”,而不是說(shuō)傳統(tǒng)ASP.NET應(yīng)用程序做不到良好的可測(cè)試性,一切皆在人為。

關(guān)于可測(cè)試性的重要性,老趙覺(jué)得已經(jīng)不需要再過(guò)多強(qiáng)調(diào)了。如果您想要獲得高生產(chǎn)力,為代碼編寫單元測(cè)試似乎已經(jīng)是必經(jīng)之路了。不過(guò)可惜的是,ASP.NET應(yīng)用程序給人的感覺(jué),始終是對(duì)可測(cè)試性不太友好,其最重要的原因之一在于對(duì)HttpContext對(duì)象的高度依賴,而我們很難對(duì)HttpContext編寫Mock或Stub:對(duì)于最常見(jiàn)的Mock框架來(lái)說(shuō),進(jìn)行Mock的方式在于對(duì)抽象類型進(jìn)行繼承和重寫,因此需要目標(biāo)類型必須能夠繼承,其成員也必須能夠重寫(override),可惜HttpContext對(duì)這兩個(gè)要求均不滿足——雖然我們有TypeMock這個(gè)強(qiáng)大的工具,只可惜它是商業(yè)產(chǎn)品。而且事實(shí)上,如果Moq等框架無(wú)法滿足您的要求,一般可以確定是設(shè)計(jì)有問(wèn)題。從這個(gè)角度說(shuō),ASP.NET圍繞HttpContext開(kāi)展的一系列功能,在設(shè)計(jì)上的確有不足之處。

因此,為了提高ASP.NET應(yīng)用程序的可測(cè)試性,各方都作了許多努力,其中的原則便是:盡可能減少對(duì)HttpContext的依賴(不可測(cè)試的邏輯),使邏輯依賴于特定的抽象類型。“特定”二字是指與您的業(yè)務(wù)或功能相關(guān)性,例如您在使用MVP模式進(jìn)行開(kāi)發(fā)時(shí),使用的每個(gè)類型都是領(lǐng)域相關(guān)(如User),或界面相關(guān)(如SelectList)的抽象類型,而不是具體的界面(如DropDownList)或協(xié)議(HttpContext1)相關(guān)類型。這往往需要您在具體類型上多加一個(gè)抽象層,針對(duì)抽象進(jìn)行編程。除了MVP模式之外,ASP.NET AJAX中的PageRequestManager也是如此,ScriptManager的各階段操作都簡(jiǎn)單地委托給了PageRequestManager,這樣不可測(cè)試的邏輯(ScriptManager)減少了,可以測(cè)試的邏輯(PageRequestManager)增加了。

不過(guò)可以想到的是,圍繞HttpContext進(jìn)行編程的場(chǎng)景也是不可避免的,例如Http Handler/Module等ASP.NET基礎(chǔ)結(jié)構(gòu),亦或是連接HttpContext與抽象類型的“黏著劑”。關(guān)于這方面微軟也在改進(jìn),例如隨ASP.NET MVC發(fā)布了ASP.NET Abstraction,其中提供了抽象類型HttpContextBase(老趙個(gè)人不喜歡Base這樣的后綴,其實(shí)更喜歡IHttpContext這樣的接口類型),這是一個(gè)赤裸裸地抽象類,其中包含了HttpContext的所有成員,個(gè)個(gè)抽象。也正是由于這樣的抽象,使得圍繞HttpContext進(jìn)行單元測(cè)試的可行性大大增加了。當(dāng)然,這句話有個(gè)前提,那就是以前圍繞HttpContext編寫的代碼,現(xiàn)在要使用HttpContextBase了,這也是提高ASP.NET應(yīng)用程序可測(cè)試性的又一原則:對(duì)于一定要依賴HttpContext的邏輯,請(qǐng)依賴HttpContextBase。那么現(xiàn)在,兄弟們就隨老趙來(lái)看一下,如何使用ASP.NET Abstraction來(lái)輔助ASP.NET開(kāi)發(fā)。

直接使用HttpContext進(jìn)行測(cè)試

HttpContext對(duì)象難以Mock,但是也并非說(shuō)它的數(shù)據(jù)我們就無(wú)法“定制”,在某些“極端簡(jiǎn)單”的情況下,我們還是可以直接構(gòu)造一個(gè)HttpContext對(duì)象進(jìn)行測(cè)試的。比如下面這個(gè)毫無(wú)意義的Http Handler:

public class CountDataHandler : IHttpHandler {public bool IsReusable { get { return true; } }public void ProcessRequest(HttpContext context){string data = context.Request.QueryString["data"];if (data == null){throw new ArgumentNullException("data");}context.Response.Write(data.Length);} }

從Query String里獲得data字段,如果沒(méi)有該字段則拋出異常,如果有就輸出它的長(zhǎng)度。這個(gè)Handler的作用就是這么無(wú)聊,只是為了做一個(gè)簡(jiǎn)單的示例。那么對(duì)它的單元測(cè)試該怎么做呢?

[TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void ProcessRequestTest_Throw_ArgumentNullException_When_Data_Is_Empty() {HttpContext context = new HttpContext(new HttpRequest("test.aspx", "http://localhost/test.aspx", ""),new HttpResponse(new StringWriter()));CountDataHandler handler = new CountDataHandler();handler.ProcessRequest(context); }[TestMethod] public void ProcessRequestTest_Check_Output() { string data = "Hello World";TextWriter writer = new StringWriter();HttpContext context = new HttpContext(new HttpRequest("test.aspx","http://localhost/test.aspx", "data=" + HttpUtility.UrlEncode(data)),new HttpResponse(writer));CountDataHandler handler = new CountDataHandler();handler.ProcessRequest(context);Assert.AreEqual(data.Length.ToString(), writer.ToString(),"The output should be {0} but {1}.", data.Length, writer.ToString()); }

它的單元測(cè)試分兩種情況,一是在data字段缺少的情況下需要拋出異常(ExpectedException),二便是正常的輸出。在測(cè)試的時(shí)候,我們通過(guò)HttpContext的一個(gè)構(gòu)造函數(shù)創(chuàng)建對(duì)象,而這個(gè)構(gòu)造函數(shù)會(huì)接受一個(gè)HttpRequest和一個(gè)HttpResponse對(duì)象。HttpRequest對(duì)象構(gòu)造起來(lái)會(huì)接受文件名,路徑和Query String;而HttpResponse構(gòu)造時(shí)只需要一個(gè)TextWriter用于輸出信息。由于我們這個(gè)場(chǎng)景過(guò)于簡(jiǎn)單,因此還真夠用了。代碼比較簡(jiǎn)單,意義也很明確,就不多作解釋了。

不過(guò)很顯然,這種簡(jiǎn)單場(chǎng)景是幾乎無(wú)法遇到的。如果我們需要POST的情況呢?做不到;如果我們需要設(shè)置UserAgent呢?做不到;如果我們要檢查Url Write的情況?做不到——統(tǒng)統(tǒng)做不到,真啥都別想做。因此我們還是無(wú)法使用這種方式進(jìn)行測(cè)試,這第一個(gè)例子僅僅是為了內(nèi)容“完整性”而加上的。

AuthorizedHandler

這個(gè)例子就復(fù)雜些了,并且直接來(lái)源于老趙以前的某個(gè)項(xiàng)目的代碼——當(dāng)然現(xiàn)在為了示例進(jìn)行了簡(jiǎn)化和改造。在項(xiàng)目中我們往往要編寫一些Handler來(lái)處理客戶端的請(qǐng)求,而同時(shí)Handler需要對(duì)客戶端進(jìn)行身份驗(yàn)證及基于角色的授權(quán),只有特定角色的客戶才能訪問(wèn)Handler的主體邏輯,否則便拋出異常。而這樣的邏輯有其固有的結(jié)構(gòu),因此我們這類Handler編寫一個(gè)公用的父類,這樣我們便可使用“模板方法”的形式來(lái)補(bǔ)充具體邏輯了。這個(gè)父類的實(shí)現(xiàn)如下:

public abstract class AuthorizedHandler : IHttpHandler {public bool IsReusable { get { return false; } }void IHttpHandler.ProcessRequest(HttpContext context){this.ProcessRequest(new HttpContextWrapper(context));}internal void ProcessRequest(HttpContextBase context){if (!context.User.Identity.IsAuthenticated){throw new UnauthorizedAccessException();}foreach (var role in this.AuthorizedRoles){if (context.User.IsInRole(role)){this.ProcessRequestCore(context);return;}}throw new UnauthorizedAccessException();}protected internal abstract void ProcessRequestCore(HttpContextBase context);protected internal abstract IEnumerable<string> AuthorizedRoles { get; } }

一般來(lái)說(shuō),我們會(huì)在IHttpHandler.ProcessRequest方法中進(jìn)行邏輯實(shí)現(xiàn),但是我們現(xiàn)在直接把方法調(diào)用轉(zhuǎn)發(fā)給接受HttpContextBase作為參數(shù)的ProcessRequest方法重載。HttpContextBase是一個(gè)抽象類型,這便是我們的測(cè)試目標(biāo)。這個(gè)方法首先判斷用戶是否經(jīng)過(guò)認(rèn)證,然后再將用戶的角色,與AuthorizedRoles抽象屬性中表示的合法角色進(jìn)行匹配,如果匹配成功則調(diào)用ProcessRequestCore抽象方法,而無(wú)論是用戶認(rèn)證還是授權(quán)失敗,都會(huì)拋出UnauthorizedAccessException異常。

這里有一個(gè)題外話:不知您是否注意到,這里沒(méi)有private方法,所有的方法都有internal修飾。這么做的原因完全是為了進(jìn)行單元測(cè)試。由于private方法無(wú)法被外部項(xiàng)目調(diào)用,因此我們只能使用internal作為修飾符,再為程序集加上InternalVisibleToAttribute標(biāo)記,把所有的internal成員向測(cè)試項(xiàng)目開(kāi)放。當(dāng)然,此時(shí)程序集內(nèi)部就能夠隨意調(diào)用那些方法了——還好,都是自家人,注意點(diǎn)便是了。

這段邏輯需要測(cè)試的環(huán)節(jié)比較多,我們依次看一下:

[TestMethod()] [ExpectedException(typeof(UnauthorizedAccessException))] public void ProcessRequestTest_Nonauthenticated_Request() {Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>(MockBehavior.Strict);mockContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(false);Mock<AuthorizedHandler> mockHandler = new Mock<AuthorizedHandler> { CallBase = true };mockHandler.Setup(h => h.ProcessRequestCore(It.IsAny<HttpContextBase>())).Throws(new Exception("ProcessRequestCore should not be called."));mockHandler.Setup(h => h.AuthorizedRoles).Throws(new Exception("AuthorizedRoles should not be accessed."));mockHandler.Object.ProcessRequest(mockContext.Object); }

這是對(duì)沒(méi)有通過(guò)身份驗(yàn)證的請(qǐng)求的回應(yīng),我們?cè)O(shè)置HttpContext.User.Identity.IsAuthenticated屬性為false,并且聲明不能碰觸到ProcessRequestCore和AuthroizedRoles屬性。在這樣的情況下,我們自然期望拋出UnauthorizedAccessException。

[TestMethod()] [ExpectedException(typeof(UnauthorizedAccessException))] public void ProcessRequestTest_Nonauthorized_Request() {Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>(MockBehavior.Strict);mockContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(true);mockContext.Setup(c => c.User.IsInRole(It.IsAny<string>())).Returns(false).Verifiable();Mock<AuthorizedHandler> mockHandler = new Mock<AuthorizedHandler> { CallBase = true };mockHandler.Setup(c => c.ProcessRequestCore(It.IsAny<HttpContextBase>())).Throws(new Exception("ProcessRequestCore should not be called."));mockHandler.Setup(c => c.AuthorizedRoles).Returns(new string[] { "admin", "user" }).Verifiable();try{mockHandler.Object.ProcessRequest(mockContext.Object);}catch{throw;}finally{mockContext.Verify();mockHandler.Verify();} }

這是測(cè)試身份驗(yàn)證通過(guò),而基于角色的授權(quán)失敗時(shí)的情況。我們把IsAuthenticated設(shè)為true,并且要求IsInRole方法在“接受到任何string類型參數(shù)”的時(shí)候都返回false,而最后再“象征性”地設(shè)置AuthorizedRoles所返回的內(nèi)容。這個(gè)測(cè)試的期望是拋出UnauthorizedAccessException,不過(guò)值得注意的是,我們的代碼還有其他要求,那就是要求IsInRole和AuthorizedRoles一定要調(diào)用過(guò)——您明白了嗎?這就是為什么對(duì)Mock對(duì)象追加Verifiable和Verify方法,并且使用try/catch/finally的緣故。

[TestMethod()] public void ProcessRequestTest_Authorized_Request() {Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>(MockBehavior.Strict);mockContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(true);mockContext.Setup(c => c.User.IsInRole(It.IsAny<string>())).Returns(false);mockContext.Setup(c => c.User.IsInRole("user")).Returns(true).Verifiable();Mock<AuthorizedHandler> mockHandler = new Mock<AuthorizedHandler> { CallBase = true };mockHandler.Setup(c => c.ProcessRequestCore(It.IsAny<HttpContextBase>())).AtMostOnce().Verifiable();mockHandler.Setup(c => c.AuthorizedRoles).Returns(new string[] { "admin", "user" }).Verifiable();mockHandler.Object.ProcessRequest(mockContext.Object);mockHandler.Verify();mockContext.Verify(); }

最后的測(cè)試自然是正常流程的測(cè)試。在這里我們要檢驗(yàn)的是正常情況下ProcessRequestCore是否“被調(diào)用,而且只被調(diào)用了一次”。如果您能夠理解前兩個(gè)測(cè)試,這個(gè)測(cè)試應(yīng)該也同樣簡(jiǎn)單才是。

UrlRewriteModule

之前都是在測(cè)試Http Handler,不過(guò)Http Module的測(cè)試也較為類似。其原則是相同的:把所有邏輯轉(zhuǎn)發(fā)給針對(duì)抽象的方法。我們這次就以最最經(jīng)典的URL重寫功能為例,如下:

public interface IUrlRewriteSource {string GetRewritePath(string rawUrl); }public class UrlRewriteModule : IHttpModule {public void Dispose() { }public UrlRewriteModule(): this(new RegexUrlRewriteSource(...)){ }internal UrlRewriteModule(IUrlRewriteSource source){this.m_source = source;}private IUrlRewriteSource m_source;public void Init(HttpApplication httpApp){httpApp.BeginRequest += (sender, e) =>{HttpContext context = ((HttpApplication)sender).Context;this.TryRewritePath(new HttpContextWrapper(context));};}internal void TryRewritePath(HttpContextBase context){string newUrl = this.m_source.GetRewritePath(context.Request.RawUrl);if (!String.IsNullOrEmpty(newUrl)){context.RewritePath(newUrl);}} }

由于測(cè)試需要,我們提取出一個(gè)IUrlRewriteSource接口。ASP.NET本身會(huì)通過(guò)無(wú)參數(shù)的構(gòu)造函數(shù)進(jìn)行創(chuàng)建,這時(shí)就會(huì)使用默認(rèn)的RegexUrlRewriteSource對(duì)象。而在測(cè)試的時(shí)候,就要?jiǎng)?chuàng)建Mock對(duì)象并通過(guò)構(gòu)造函數(shù)的重載進(jìn)行“依賴注入”了。在Init方法中我們直接使用匿名委托來(lái)作為BeginRequest事件的處理函數(shù),而其中就把邏輯直接委托給TryRewritePath方法了。TryRewritePath方法會(huì)判斷Source中得知是否需要進(jìn)行URL重寫,并且在需要的時(shí)候調(diào)用RewritePath方法。它的測(cè)試如下:

[TestMethod] public void TryRewritePathTest_No_Rewrite() {Mock<IUrlRewriteSource> mockSource = new Mock<IUrlRewriteSource>();mockSource.Setup(s => s.GetRewritePath(It.IsAny<string>())).Returns<string>(null).Verifiable();Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>(MockBehavior.Strict);mockContext.Setup(c => c.Request.RawUrl).Returns("Hello");mockContext.Setup(c => c.RewritePath(It.IsAny<string>())).Throws(new InvalidOperationException("Should not call the RewritePath method."));UrlRewriteModule module = new UrlRewriteModule(mockSource.Object);module.TryRewritePath(mockContext.Object);mockSource.Verify(); }[TestMethod] public void TryRewritePathTest_Rewrite_Article_Detail_Page() {string rawUrl = "Article/5";string targetUrl = "~/Article.aspx?id=5";Mock<IUrlRewriteSource> mockSource = new Mock<IUrlRewriteSource>();mockSource.Setup(s => s.GetRewritePath(It.IsAny<string>())).Throws(new InvalidOperationException("Why so many unnecessary method calls?"));mockSource.Setup(s => s.GetRewritePath(rawUrl)).Returns(targetUrl).Verifiable();Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>(MockBehavior.Strict);mockContext.Setup(c => c.Request.RawUrl).Returns(rawUrl);mockContext.Setup(c => c.RewritePath(targetUrl)).Verifiable();UrlRewriteModule module = new UrlRewriteModule(mockSource.Object);module.TryRewritePath(mockContext.Object);mockSource.Verify();mockContext.Verify(); }

在不需要重寫的情況下,IUrlRewriteSource對(duì)象的GetRewritePath方法永遠(yuǎn)返回null,而此時(shí)也不應(yīng)該調(diào)用HttpContext的RewritePath方法。否則,便判斷給出合適的RawUrl和重寫目標(biāo),并判斷RewritePath方法有沒(méi)有正確調(diào)用過(guò)便是。其實(shí)單元測(cè)試就這么簡(jiǎn)單。

結(jié)束

沒(méi)啥想說(shuō)的,就這么結(jié)束吧。

您有什么想法嗎?說(shuō)說(shuō)看吧。

轉(zhuǎn)載于:https://www.cnblogs.com/JeffreyZhao/archive/2009/04/23/improve-asp-net-testability-via-abstractions.html

總結(jié)

以上是生活随笔為你收集整理的使用ASP.NET Abstractions增强ASP.NET应用程序的可测试性的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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