<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      代碼改變世界

      對ASP.NET MVC項目中的視圖做單元測試

      2009-02-25 01:01  Jeffrey Zhao  閱讀(19425)  評論(46)    收藏  舉報

      關于視圖的單元測試

      說到ASP.NET MVC,我們似乎始終都在關注對于Controller的測試——雖然Stephen Walther也寫過如何脫離Web Server對View進行單元測試,但是他的方法可看而不可用。復雜的構造和預備,以及對生成的HTML字符串作判斷——這真是在對視圖做單元測試嗎?仔細分析他的代碼可以發現,這其實是在對ViewEngine做單元測試。而且,如果真要對ViewEngine做單元測試,也不應該像他那樣依賴外部文件。在我看來,他的做法什么都不是……似乎美觀,似乎能博得一些“掌聲”,但是這個掌聲是來自于他的解決方案,還是大家一時的沖動呢?

      如果要對視圖做單元測試,還是要將內容呈現在瀏覽器中才行。在對網頁做單元測試時,我們一般會使用WatiN等工具操作瀏覽器,打開頁面,再對其DOM元素結構及內容作斷言。不過……這是單元測試嗎?可惜這只能算是一種回歸測試或用戶驗收測試。因為,我們在打開一個頁面的時候,從表現層到業務邏輯再到數據訪問,應用程序的每個部件都在忙碌著。而單元測試講究的是“分離”,分離一切關注,分離一切依賴。因為分離,我們才能準確定位錯誤;因為分離,我們才能在測試中使用我們準備好的數據。

      既然要分離,我們就必須遵循一定的使用規范。在《ASP.NET MVC單元測試最佳實踐》中我提到,在View中只能使用ViewData中的數據,而不該依賴其他內容(包括HttpContext)。這樣我們就可以自行構造ViewData并注入一個視圖對象中。事實上,這個約定在ASP.NET MVC自帶的項目模板中就被破壞了。請看Views\Shared\LogOnUserControl.ascx,其中通過this.User來查看當前用戶的登陸狀態。這是個定義在傳統Page對象上的屬性,從當前HttpContext上直接獲取。如果使用這種方式,我們在單元測試時就難以“模擬”當前用戶的登陸狀態,進而難以使測試覆蓋到測試的各種情況了。

      Lightweight Test Automation Framework

      在這里,老趙推薦使用ASP.NET Team提供的Lightweight Test Automation Framework(下文稱之為LTAF)作為測試工具,它目前已經在CodePlex上更新至Feb Update版本。這個框架的作用與WatiN和Selenium類似,可操作瀏覽器對應用程序編寫回歸測試。雖然在某些方面(例如DOM元素的選取)不如“競爭對手”,但是LTAF自有其獨到之處:

      • 由于直接在瀏覽器中運行,它天生便支持現有的——以及未來可能出現的任意瀏覽器。
      • 由于直接部署在被測試的網站中,因此測試代碼和網站頁面是在同一個進程中。

      第一點優勢自不必說,而第二點更是關鍵。試想WatiN和Selenium,都是通過編寫代碼在瀏覽器中打開頁面。這意味著我們的在測試代碼和被測試的網頁分別在不同的進程中。在這個前提下,如果我們要將測試代碼中定義的數據傳遞給被測試的網頁(也就是視圖對象),我們就必須進行跨進程的通信。而無論怎么實現,都逃不過“序列化”一途,這無疑增加了復雜度。而使用LTAF之后,這個問題瞬間煙消云散了,因為我們可以直接在內存中“傳遞”測試數據,一切都只是個引用而已。

      不過任何事物都具有兩面性,LTAF也有一些難以天生的,而且是永遠無法彌補的缺點。例如:

      • 由于LTAF將待測試的頁面放置在Frame中,因此該頁面上的window.top等基于瀏覽器frame結構的屬性會被改變。
      • 由于LTAF的本質是使用JavaScript來操作DOM,這意味著任何會阻塞程序進行的操作(例如alert)都不能使用,否則將阻塞整個測試過程。

      不過幸運的是,這兩點都不回成為嚴重的問題。對于第一種,我們只需要編寫一個自定的getTop方法來替換直接訪問windows.top的做法即可。而第二種情況——老趙從來不喜歡alert或confirm這種“純瀏覽器功能”,因為它們會帶來很差的用戶體驗,更何況現在的JavaScript類庫/框架都能很輕松的做出這種效果,您覺得呢?

      LTAF的具體使用方式可參考其Release Note。令人奇怪的是,老趙發現直接在項目中使用LTAF會有一些小問題(不過它的示例為什么就一切正常呢?),因此進行了一些細微的修改。請注意~\UnitView\DriverPage.aspx文件尾部的一些JavaScript代碼。

      UnitView的使用

      于是老趙編寫了一個組件UnitView,方便我們構造一個單元測試時所需的數據。有了數據,便能夠直接將視圖在瀏覽器中加以呈現了。例如:

      [WebTestClass]
      public class HomeTests
      {
          [WebTestMethod]
          public void LoggedOnIndexTest()
          {
              var data = new TestViewData<IndexModel>
              {
                  ControllerName = "Home",
                  ActionName = "Index",
                  Model = new IndexModel
                  {
                      Message = "Welcome guys!",
                      Identity = new UserIdentity
                      {
                          IsAuthenticated = true,
                          Name = "Jeffrey Zhao"
                      }
                  }
              };
      
              HtmlPage page = new HtmlPage(TestViewData.GenerateHostUrl(data));
      
              // Assert title
              Assert.AreEqual("Home Page", page.Elements.Find("title", 0).GetInnerText());
      
              // Assert head element
              var mainContent = page.Elements.Find("main");
              var head2 = mainContent.ChildElements.FindAll("h2").Single();
              Assert.AreEqual(data.Model.Message, head2.GetInnerText(), "Message should be displayed.");
      
              var loginTabInnerText = page.Elements.Find("logindisplay").GetInnerTextRecursively();
              Assert.IsTrue(loginTabInnerText.Contains("Welcome"), "'Welcome' missed.");
              Assert.IsTrue(loginTabInnerText.Contains(data.Model.Identity.Name), "Login name missed.");
          }
      }
      

      自然,Web Server是不可或缺的。幸運的是,分離讓我們的視圖只會涉及最簡單的測試數據,這樣VS自帶的簡單Web Server就足夠了。在上面的代碼中,我們直接構造了強類型的TestViewData對象,它包含呈現一個視圖所需要的所有數據:

      • Cotroller和Action名稱。從理論上說,由不同的Controller和Action進入同樣的視圖可能會得到不同的結果。
      • View和Master名稱。如果省略,則表明將使用默認的視圖,即通過Controller和Action的值來確定。
      • ViewData和Model。

      TestViewData.GenerateHostUrl方法會把data保存起來,并返回一個URL。訪問該URL便能夠得到對應的視圖內容。

      如果您想使用UnitView,可以從上面的鏈接中下載UnitView的源代碼和示例在本機進行嘗試。使用UnitView時主要有以下幾個注意點:

      1. 將Tests項目的輸出路徑指向被測試網站的bin目錄,這樣既可以在運行時得到正確的程序集,又不必為網站添加多余的引用。
      2. 將~\UnitView目錄復制到您的網站根目錄下(在發布網站時,請剔除該目錄)。如果想使用其它目錄,請關注接下來UnitView實現分析。
      3. 編輯~\UnitView\Web.config文件,將MvcApp.Tests.dll修改為您自己的包含測試代碼的程序集。

      UnitView實現分析

      UnitView組件非常簡單,簡單地幾乎不值一提。TestViewData類型包含了測試需要的所有數據,而TestViewData<TModel>繼承了TestViewData,提供了強類型的Model屬性訪問方式。它們就不作分析了。

      此外,TestViewData還有一些靜態方法:

      public class TestViewData
      {
          static TestViewData()
          {
              PersistentProvider = new InProcPersistentProvider();
          }
      
          public static IPersistentProvider PersistentProvider { get; set; }
      
          public static string GenerateHostUrl(TestViewData data)
          {
              var key = PersistentProvider.Save(data);
              return ViewHostHandlerUrl + "?key=" + HttpUtility.UrlEncode(key);
          }
      
          private static string ViewHostHandlerUrl
          {
              get
              {
                  return ConfigurationManager.AppSettings["UnitView_ViewHostHandlerUrl"]
                      ?? "/UnitView/ViewHostHandler.ashx";
              }
          }
      
          internal static TestViewData Load(string key)
          {
              return PersistentProvider.Load(key);
          }
          ...
      }
      

      GenerateHostUrl方法將委托PersistentProvider保存對象,并得到一個key。這個key將拼接在ViewHostHandlerUrl屬性上,這便是被測試的路徑。從代碼中可以看出,如果您不想使用默認的測試路徑,只需在web.config的AppSettings節點中添加一個目標地址即可。

      PersistentProvider屬性為IPersistentProvider接口類型,其中定義了Save/Load/Remove三個方法。IPersistentProvider在項目中只有一個實現:InProcPersistentProvider,它會將TestViewData存放在內存中的一個字典里。這個實現已經足夠讓UnitView結合LTAF運行(LTAF的同進程特性起到了關鍵的作用)。不過,如果您還是希望使用WatiN等獨立進程的測試工具,就必須實現自己的IPersistentProvider類型。例如您可以實現一個FilePersistentProvider,將TestViewData序列化至一個外部文件中,這樣就可以在合適的時候將它取回了。

      另一個較為關鍵的類型是UnitView.Engine.ViewHostHandler:

      public class ViewHostHandler : IHttpHandler
      {
          private HttpContext Context { get; set; }
      
          public void ProcessRequest(HttpContext context)
          {
              this.Context = context;
      
              ControllerContext controllerContext = new ControllerContext(
                  new HttpContextWrapper(context),
                  this.Data.RouteData,
                  new MockController());
      
              new ViewResult
              {
                  MasterName = this.Data.MasterName,
                  ViewName = this.Data.ViewName,
                  TempData = this.Data.TempData,
                  ViewData = this.Data.ViewData,
              }.ExecuteResult(controllerContext);
          }
      
          private string Key
          {
              get
              {
                  string key = this.Context.Request.QueryString["key"];
                  if (String.IsNullOrEmpty(key))
                  {
                      throw new ArgumentNullException("key");
                  }
      
                  return key;
              }
          }
      
          private TestViewData m_data;
          private TestViewData Data
          {
              get
              {
                  if (this.m_data == null)
                  {
                      this.m_data = TestViewData.Load(this.Key);
                      if (this.m_data == null)
                      {
                          throw new ArgumentNullException("Cannot retrieve the data.");
                      }
                  }
      
                  return this.m_data;
              }
          }
      
          public bool IsReusable { get { return false; } }
      }
      

      首先,在ProcessRequest方法會取回TestViewData,并根據這些數據構造一個ViewResult對象,最后執行它的ExecuteResult方法來輸出視圖內容。由于ExecuteRequest方法的需要,我們還必須構造一個ControllerContext對象,也就意味著我們還必須提供一個Controller對象和HttpContext的封裝。從代碼中可以看出,我們這里使用了最簡單的數據。由于視圖遵守“約定”,它只會從ViewData中獲取數據,所以無論Controller或HttpContext是什么值都已經無關緊要了。

      您可能會想,為什么會有這樣的“約定”,不讓視圖從HttpContext對象中獲取數據呢?Mock一個HttpContext對象也不是那么困難(這里要感謝各種強大的Mock框架)啊。可惜,Mock后的HttpContext很難進行序列化,這樣就幾乎杜絕了跨進程通信的可能,這對于使用WatiN和Selenium進行測試的朋友們無疑是一種災難。權衡之下,老趙決定放棄對HttpContext的支持。

       

      注1:目前UnitView基于ASP.NET MVC RC構建,當RTM發布后我會進行必要的更新。請關注老趙這篇文章和托管在MSDN Code Gallery上的代碼(http://code.msdn.microsoft.com/UnitView)。

      注2:在《ASP.NET MVC單元測試最佳實踐》中我也包含了UnitView組件,實現略有不同——請以本篇文章為主。

      主站蜘蛛池模板: 国产欧美亚洲精品a第一页| 久久亚洲国产成人亚| 亚洲国产精品无码一区二区三区| 亚洲国产制服丝袜高清在线| 亚洲国产良家在线观看| 久久99亚洲网美利坚合众国| 开心激情站一区二区三区| 精品无码久久久久久尤物| 成人影片一区免费观看| 亚洲综合国产伊人五月婷| 五十路久久精品中文字幕| 亚洲视频一区| 欧美人与zoxxxx另类| 中文无码热在线视频| 国产av一区二区三区久久| 吉川爱美一区二区三区视频| 国产成人午夜福利院| 国产精品乱一区二区三区| 九九热在线免费播放视频| 亚洲午夜香蕉久久精品| 亚洲成人午夜排名成人午夜| 亚洲av无码牛牛影视在线二区 | 日韩精品人妻av一区二区三区| 99精品国产兔费观看久久99| 美女内射无套日韩免费播放| 金坛市| 日韩不卡一区二区在线观看| 亚洲欧美日韩综合在线丁香| 国产色无码专区在线观看| 黑人av无码一区| 亚洲精品综合第一国产综合| 亚洲人成电影网站 久久影视| 亚洲性一交一乱一伦视频| 日韩丝袜人妻中文字幕| 四虎在线成人免费观看| 欧洲精品色在线观看| 最新亚洲春色av无码专区| 不卡免费一区二区日韩av| 综合激情网一区二区三区| 四虎国产精品永久在线| 一区二区亚洲人妻av|