Windows phone 應用開發[9]-單元測試

本篇來談談Windows phone Unit Test.html

原來在9月份一次線下技術沙龍現場交流.我在現場提到關於Windows phone Unit Test在實際編程所體現一些問題.惋惜當時在現場迴應人的太少.經過本篇將詳細梳理關於在Windows phone 開發流程作UT可能遇到的問題,以及一些具體解決方案.程序員

關於UT.不會在這裏拿太多篇幅解釋它基本的用法.固然也更不會拿時間去強調UT它在實際編程中保證軟件質量重要性.從自身角度來講.一個程序員良好的職業素養每每源自於對自身高要求,並能鍥而不捨的保持下去.在實際開發流程照成不少」不愉快「的體驗,其實不少從自身角度來講徹底能夠避免的.sql

其實不少Team在實際開發中拒絕寫UT.並且還不在少數.依然仍是不少開發人員認爲本身只是不斷貢獻產品的Code.而和UT無關.仍是有那麼多Program Manager太過於專一開發進度.在每次CodeReview後.忽略了爲UT留下相應的時間.而在後期集成測試階段讓開發人員陷入Bug突顯修修補補「災難」之中難以自拔. 顯然實際開發中突顯種種問題.是對理想狀態下軟件工程必要流程斷章取義.而IDE開發工具愈來愈強大編譯能力彷佛讓開發人員產生依賴.編譯經過只是說明語法正確.而沒法真實確認實際Code語義是否也是願景一致.而在具備必定規模存在多分枝項目結構中.若是沒有一個完整保證軟件質量的體系和具體措施方法.很難想象這樣集成項目中對開發人員該是一種什麼樣的災難.!?編程

well.談到Windows phone應用或是客戶端.每每實際開發規模相對於Pc Application較小. 特別是將來突出雲平臺發展方向.必然會照成客戶端APP愈來愈瘦的趨勢.但必要測試依然是構造可靠應用程序必經之路.windows

針對Windows phone應用程序Unit Test 官方並無在IDE提供對應的測試框架.通過實際開發反覆驗證.依然能夠經過以下幾種方式創建.Windows phone 單元測試.:服務器

創建單元測試:網絡

[1]經過帶有官方背景的Jeff Wilcox’s 更新Silverlight Unit Test Framework的Windows phone版本創建單元測試.具體請參見Updated Silverlight Unit Test Framework bits for Windows Phone and Silverlight 3 And Unit Testing Silverlight & Windows Phone Applications多線程

[2]經過第三方測試框架Windows phone Test FrameWorkNUnit For Windows phone 等構建app

在目前Windows phone應用程序中創建單元測試框架中在開發者羣體使用最普遍的是Jeff Wilcox’s 維護更新的Silverlight Unit TEst FrameWork For Windows phone版本.其實熟悉Silverlight開發的同窗應該知道.Jeff Wilcox是Silverlight 2版本時官方推出Unti Test FrameWork單元測試框架的主要開發人員之一.作過Silverlight單元測試的開發人員確定知道他曾在博客寫的Silverlight 2版本單元測試系列.框架

在Windows phone應用程序中引用單元測試須要添加以下引用DLL:

  
  
  
  
  1. //添加引用DLL   
  2. Microsoft.Silverlight.Testing   
  3. Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight 

能夠經過兩種方式得到該DLL引用.方式一在Jeff Wilcox’s 博客下載 解壓便可:

[ZIP, 518K] Silverlight Unit Test Framework Assemblies compatible with Mango Beta Tools

下載完成後.解壓能看到如上兩個必須的DLL.這時解決方案添加一個普通的Windows phone Application項目.[UT測試結果須要在UI輸出].手動添加如上兩個DLL引用關係.這時會提示:

提示引用Silverlight類庫.能夠不理會提示直接點擊是肯定引用.

方式二: 打開Visual Studio 2011 找到Tool->Extension Manager .在Online Gallery選項中搜索:Windows phone Test Project 能夠看到:

能夠直接經過點擊Download下載安裝該項目模板.安裝完成後新建Project就能看到 Test選項頁下多了一個Windows phone Test Project模板:

新建一個測試項目命名Test project1[測試用].在執行編譯前須要安裝Nuget而後經過Tool->Library Package Manager->Package Manager Console窗口輸入以下命令添加引用:

這時會在TestProject 1實際上會看到添加三個引用:

  
  
  
  
  1. //添加引用DLL    
  2. Microsoft.Silverlight.Testing   
  3. Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight 4: SilverlightSerializer.WP7 

注意.當創建玩這個模板項目TestProject1會提示經過Nuget工具執行以下命令行: Install-Package Silverlight.UnitTest 和 Install-Package WindowsPhoneEssentials.Testing執行兩個指令 .在執行命令前前者因WP7SDK 更新的緣由,前者並不支持Mango版本因此就不推薦使用了.一概使用後者命令初始化引用庫.

構建好測試項目後.首先在Windows phone Unit TEst中.咱們既能夠採用極限編程XP提倡的[TDD]Test Driver Development測試驅動的方式從上而下進行.也能夠僅僅只是回顧性的編寫單元測試一遍驗證代碼的執行是否與預期的執行效果相同.本篇主要採用Silverlight Unit TEst FrameWork來構建執行WP7單元測試.

Well.在構建單元測試用例[Test Case]前.須要構建一個具備完成功能的Windows phone應用程序.這裏爲了演示的目的.當前應用程序以MVVM的方式實現UI上一個分類列表的顯示.首先定義一個ViewModel-MainPage_ViewModel 內容:

   
   
   
   
  1. public class MainPage_ViewModel:BasicViewModel   
  2. {   
  3. ObservableCollection<CatalogInfo> catalogInfoCol = new ObservableCollection<CatalogInfo>();   
  4. public ObservableCollection<CatalogInfo> CatalogInfoCol   
  5. {   
  6. get { return this.catalogInfoCol; }   
  7. set   
  8. {   
  9. this.catalogInfoCol = value;   
  10. base.NotifyPropertyChangedEventHandler("CatalogInfoCol");   
  11. }   
  12. }   
  13. public void LoadCatalogDefaultData()   
  14. {   
  15. this.catalogInfoCol.Clear();   
  16. this.catalogInfoCol.Add(new CatalogInfo() { CatalogName="Music & Video",CatalogComment="For Everyone Catalog" });   
  17. this.catalogInfoCol.Add(new CatalogInfo() { CatalogName = "Book References", CatalogComment = "just For Child" });   
  18. }   
  19. public string catalogTitle;   
  20. public string CatalogTitle   
  21. {   
  22. get { return this.catalogTitle; }   
  23. set   
  24. {   
  25. this.catalogTitle = value;   
  26. base.NotifyPropertyChangedEventHandler("CatalogTitle");   
  27. }   
  28. }   
  29. }   
  30. public class CatalogInfo   
  31. {   
  32. public string CatalogName{get;set;}   
  33. public string CatalogComment{get;set;}   

這裏定義一個ObserverCollection<T>來實現UI界面的綁定.數據源爲了演示目的 採用靜態添加集合方式.添加數據.創建號ViewModel 添加UI綁定:

    
    
    
    
  1. private MainPage_ViewModel mainPage_ViewModel = null;   
  2. void MainPage_Loaded(object sender, RoutedEventArgs e)   
  3. {   
  4. if (this.mainPage_ViewModel == null)   
  5. this.mainPage_ViewModel = new MainPage_ViewModel();   
  6. this.mainPage_ViewModel.LoadCatalogDefaultData();   
  7. this.DataContext = mainPage_ViewModel;   

在UI中添加一個ListBox呈現數據直接運行效果以下:

至此一個簡單以MVVM形式構建分類列表顯示功能Windows phone 應用程序構建完成了.在構建單元測試以前.原結構化編程語言中,好比C,要進行測試的單元通常是函數或子過程.但在目前的OOP面向對象的概念中,單元測試對應基本單位就是類.可是實際操做發現.類做爲測試單位,複雜度高,可操做性較差,所以仍然主張以函數做爲單元測試的測試單位,但能夠用一個測試類來組織某個類的全部測試函數. 相對於Windows phone 應用程序以MVVM模型以及UIBind引擎中.核心代碼更加傾向於集中ViewMolde和UI的Code-Behind中.因Silverlight Unit test FrameWork[SUTF]框架對單元測試具備可視化的輸出.因此必須基於Windows phone Application模板.要在單元測試的項目構建測試用例前.須要初始化SUTF測試結果用戶顯示界面.

在測試項目中UnitiyCommonEmptyDemo.Test的MainPage 的Loaded事件中須要作以下幾件事:

處理視圖:

[1]隱藏SystemTray系統托盤

[2]處理應用程序的BackPress事件在SUTF中創建單元輸出視圖切換

[3]把當SUTF的測試結果輸出當前UI中

實現以下:

  
  
  
  
  1. void MainPageOutPut_Form_Loaded(object sender, RoutedEventArgs e)   
  2. {   
  3. //UnAvaliable SystemTray   
  4. SystemTray.IsVisible=false;   
  5. var currentMobileTestPage = UnitTestSystem.CreateTestPage() as IMobileTestPage;   
  6. if (currentMobileTestPage != null)   
  7. {   
  8. BackKeyPress += (x, se) => se.Cancel = currentMobileTestPage.NavigateBack();   
  9. (Application.Current.RootVisual as PhoneApplicationFrame).Content = currentMobileTestPage;   
  10. }   

針對應用程序的功能.須要經過Unit TEst 驗證CatalogInfoCol是否觸發了PropertyChanged通知事件.綁定UI集合是否具備數據? 在修改CatalogTitle過程當中是否正確傳遞屬性的值?.

有了如上兩個測試用例.針對對應MainpageUI創建MainPageTestHelper並表示類[TestClass]特性. 首先驗證CatalogInfoCol是否觸發通知事件.並在值發生變化集合中是否具備數據.創建第一個TEstCase:

   
   
   
   
  1. [TestMethod]   
  2. public void DataColIsChanged_Test()   
  3. {   
  4. bool isPropertyChanged = false;   
  5. MainPage_ViewModel currentViewModel = new MainPage_ViewModel();   
  6. currentViewModel.PropertyChanged += (x, se) =>    
  7. {   
  8. if(currentViewModel.CatalogInfoCol.Count>0)   
  9. isPropertyChanged = true;   
  10. };   
  11. currentViewModel.CatalogInfoCol = new System.Collections.ObjectModel.ObservableCollection<CatalogInfo>()    
  12. {   
  13. new CatalogInfo(){CatalogName="ComplateTestChanged",CatalogComment="TestData"}   
  14. };   
  15. Assert.IsTrue(isPropertyChanged);   

當對ViewModel屬性賦值觸發PropertyChanged事件.並判斷當前集合是否存在數據.一樣.修改CatalogTitle看額外的修改是否正確傳遞屬性對應的值,創建對應的Test Case 以下:

  
  
  
  
  1. [TestMethod]   
  2. public void DataCatalogTitle_CatalogTitle_Test()   
  3. {   
  4. bool isEventChanged = false;   
  5. MainPage_ViewModel currentViewModel = new MainPage_ViewModel();    
  6. currentViewModel.PropertyChanged += (x, se) =>    
  7. {   
  8. if(currentViewModel.catalogTitle.Equals("newTitle"))   
  9. isEventChanged=true;   
  10. };   
  11. currentViewModel.CatalogTitle = "newTitle";   
  12. Assert.IsTrue(isEventChanged);   

ok.編譯經過。運行結果:

在SUTF中對應類和函數 測試結果之間具備必定層級關係.點擊進入每一個TestMethod具體的測試詳情:

well.也能夠寫一個測試出錯的函數來看看在出錯是SUTF表現.添加TestCase 模擬出錯的狀況 添加以下Code:

  
  
  
  
  1. [TestMethod]   
  2. [Description("This test always fails intentionally")]   
  3. public void AllwaysWrong()   
  4. {   
  5. Assert.IsTrue(false,"Test Method For Wrong Case!");   

編譯經過 運行:

帶有紅點是沒有經過測試的類.單擊類名能夠找到類中帶有TestMethod特性的方法列表.能在測試結果詳情頁看到對應TestMethod對應Description描述,測試的結果 運行時間和對應的異常信息.而能在異常信息中也能看到咱們Code預先設置出錯時會顯示ExceptionMessage字符串提示.

如上在Windows phone application 構建一個最簡單單元測試整個流程.

在Windows phone 應用開發中經常須要經過網絡協議獲取數據.或是經過異步操做實現經常使用UI更新等.這也是最爲常見極爲頻繁的異步操做.其實作過Silverlight Application集成測試的同窗應該知道這每每大量異步操做照成測試過程不少難易規避的問題.

和大多數單元測試框架不一樣.Silverlight Unit Test FrameWork整個單元測試框架是運行相同的線程上的.若是應用程序引用任何外部服務相似一個WCF Service都須要一個返回的UI線程的異步調用. 致使在UT同一線程執行時沒法阻止當前線程等待WCF調用返回結果.UT沒法作.

針對Windows phone Application應用程序. 若是想作集成測試基本不太可能.Silverlight Unit Test Framework 經常由於進程之間互操做出現任何未處理的異常都會中斷整個集成測試的運行.而集成測試經常也須要長時間.跨越多線程操做的. 經常在運行時會出現異常後會自動跑到App.cs中Debugger.Break()方法中斷整個程序執行當即退出.沒有任何提示.而不是徹底預期想UT測試返回Fail結果.

well.其實在Silverlight Unit Test Framework 框架對異步操做作UT徹底可行的.只是存在一些測試用例中經常容易出錯問題.出錯頻率較高.如上應用擴展一下.把ViewModel中集合經過異步方式獲取數據源.

在獨立封裝UnitiyCommon 類庫中定義CommentAPI類用來獲取網絡上數據.定義Code以下:

  
  
  
  
  1. public delegate void CommentData(List<CommentInfo> commentList, Exception se);   
  2. public static event CommentData LoadCommentDataComplated;   
  3.  
  4. /// <summary>   
  5. /// This Method Simulate asynchronous request    
  6. /// </summary>   
  7. /// <param name="uri">Request Download Image Uri</param>   
  8. public static void GetAllNewsCommentOperator(object uri)   
  9. {   
  10. if (!string.IsNullOrEmpty(uri.ToString()))   
  11. {   
  12. //Single Subscribe   
  13. LoadCommentDataComplated = null;   
  14. BasicAPI.TransportWebRequestOperator("POST", uri.ToString(), RequestComent_CallBack);   
  15. }   

如上程序的目的經過一個指定的URI獲取網絡上圖片數據.這個過程是異步的.封裝類庫中.要UI進行交互則使用最原始簡單的委託+事件的組合方式.當圖片數據下載完成經過LoadCommentDataComplated事件通知執行操做. 下載圖片數據成功後.回調函數以下:

  
  
  
  
  1. static void RequestComent_CallBack(IAsyncResult result)   
  2. {   
  3. try   
  4. {   
  5. HttpWebRequest currentRequest = result.AsyncState as HttpWebRequest;   
  6. WebResponse currentResponse = currentRequest.EndGetResponse(result);   
  7. if (currentResponse != null)   
  8. {   
  9. //Update State   
  10. IsComplated = true;   
  11. CommentInfo downloadComment = new CommentInfo()   
  12. {   
  13. CommentName = "Comment Image",   
  14. CommentImageUri=currentRequest.RequestUri.AbsoluteUri,   
  15. CommentImageData = currentResponse.GetResponseStream()   
  16. };   
  17. List<CommentInfo> commentList = new List<CommentInfo>(){downloadComment};   
  18. if (LoadCommentDataComplated != null)   
  19. LoadCommentDataComplated(commentList, null);   
  20. }    
  21. }   
  22. catch (Exception se)   
  23. {   
  24. if (LoadCommentDataComplated != null)   
  25. LoadCommentDataComplated(null, se);   
  26. }   

回調函數手動處理數據.爲了處理Unit Test單元測試.針對單元測試採用EnqueueCallback對象.須要額外添加以下Code:

   
   
   
   
  1. public static bool IsComplated { get; private set; }   
  2. public void UpdateAsync()   
  3. {   
  4. System.Threading.ThreadPool.QueueUserWorkItem(GetAllNewsCommentOperator);   

UpdateAsync方法的目的是經過Threadpool進程池的方式.在執行單元時調用.把全部的異步操做封裝隊列方式並稍後執行,.封裝號CommentAPI後.經過ViewModel與UI進行關聯.這裏Code略去.詳見源碼.篇幅限制 不在贅述. 綁定UI後運行執行的結果以下:

如上其實我哪了一個最簡單而最多見WebRequest異步請求方式獲取網絡數據.如何在Silverlight Unit Test FrameWork中對這種異步操做作單元測試?

其實原來Silverlight Unit Test FrameWork在第一個版本時並不支持對異步操做.後來確實太多開發人員發現不少核心的業務在異步中沒法實現UT.Jeff Wilcox在後續版本增長對異步操做支持 .關於實現的過程Jeff Wilcox在其博客中有一篇Blog說的很是清楚:

Asynchronous Support For SUTF:
Asynchronous test support – Silverlight unit test framework and the UI thread

在Silverlight Unit Test Framework執行過程隨着時間遷移執行以下:

從圖中輕易發現SUTF框架要面臨的問題,相對桌面Silverlight應用成不一樣的.SUTF要把可能在不一樣線程中異步調用操做.在時間軸可以以相似同步方式按照隊列加以排序執行.關於這個執行規則組成.能夠經過一系列UT中操做步驟完成. 那咱們UT要完整測試一個異步調用 須要執行以下步驟:

異步測試須要執行的步驟:

[1]:首先經過線程池TheadPool把全部異步操做封裝.在隊列中隨着時間軸線稍後執行.在UT中經過調用該方法開發異步調用

[2]:EnqueueCallback()方法添加一個任務到執行隊列中.

[3]:EnqueueConditional()方法添加一個條件判斷隊列,若是爲true才繼續執行

[4]: EnqueueDelay() –添加指定的隊列等待時間

[5]: EnqueueTestComplete() 添加一個TestComplete()到隊列中,這個方法告知framework測試結束了

具體的執行流程以下:

[以下章節.是在7份醉意下寫的. 有些細節可能寫的有些粗糙.]

梳理好了在測試框架中整個測試異步Begin-End模型流程.按照該流程執行.新建一個測試類MainPageAsyncTestHelper.首先針對異步測試須要引用經常使用的EnqueueCallback、EnqueueDelay等對象.該類須要繼承Microsoft.Silverlight.Testing;空間下SilverlightTes類.以便引用,實現核心Code:

  
  
  
  
  1. [TestClass]   
  2. public class MainPageAsyncTestHelper:SilverlightTest   
  3. {   
  4. [TestMethod]   
  5. [Asynchronous]   
  6. [Description("Test Async Operator .")]   
  7. [Timeout(6000)]   
  8. public void AsyncOperator_ViewModel_Test()   
  9. {   
  10. CommentAPI currentCommentAPI = new CommentAPI();   
  11. bool isAsnycComplated = false;   
  12. CommentAPI.LoadCommentDataComplated += (x, se) =>    
  13. {   
  14. isAsnycComplated = true;   
  15. };    
  16.  
  17. //Test Async    
  18. EnqueueCallback(() => { currentCommentAPI.UpdateAsync(); });   
  19. EnqueueConditional(() => isAsnycComplated);   
  20. EnqueueCallback(() => Assert.IsFalse(CommentAPI.IsComplated));   
  21. EnqueueTestComplete();    

在異步測試方法中.可選的特性項.針對異步操做測試方法必須添加[Asynchronous]標識.Description特性用來描述當前測試方法測試的功能簡介描述.

而對於Timeout用意.你們都應該知道Begin-End異步模型中.若是創建網絡請求可能致使請求超時狀況發生.並且是服務器被動限制的.而在單元測試過程當中.咱們也不得不考慮當前單元測試可能會失敗.可能在執行異步過程當中會卡在一個無線循環或是相似請求的狀態中.此類狀態會使測試的執行耗費太長的時間.特別在執行集成測試中這種現象最爲明顯和常見.固然做爲單元測試.儘可能保證功能完整正確.特別在使用ASynchronous特性標識.若是在執行EnqueueConditional時從未使其條件語句爲真.致使測試用例可能會被無限期鎖住.固然爲了是測試流程中避免出現中斷測試或測試用例沒法所有執行下去狀況發生.Timeout特性爲執行測試方法的時間提供一個上限值. 若是測試方法超過該時間則認定爲失敗.

Well.經過測試用例中.首先創建一個標識屬性isAsnycComplated 用來標識當前異步操做是否完成.這是做爲EnqueueCallBAck對象執行隊列中必備的執行條件.首先經過UpdateAsync()方法啓動異步方法. 再經過IsAsnyncComplated指定執行條件. Assert對齊進行排列.最後經過EnqueueComplete()方法來指示當前測試方法結束.

編譯經過.測試結果:

異步測試經過.

本篇其實原不想寫這麼多篇幅.在Windows phone 中開始作Unit Test和集成測試也因傳統的異步Begin-End模型會在實際操做出現不少異常.本篇目的是演示Windows phone 中作UT主要方式.以及處理這個過程本身碰到一些具體問題尋求的實際解決方案.拋磚引玉.但目前集成測試中一個解決方案是始終經過EnqueueCallback確保異常恰當地報告給單元測試框架。只要一個錯誤就能中斷接下來的全部測試.而引發這個問題根源主要源於Windows phone不少操做異步模型致使.固然關於集成測試出錯比較頻繁的狀況.國外一個做者Richard Szalay在其經過RX[Reactive Extensions]結合單元測試 給出一個處理解決方案. 這篇文章連接以下:

Richard Szalay 集成解決方案:

Writing asynchronous unit tests with Rx and the Silverlight Unit Testing Framework

在實際開發中其實咱們項目中採用三種測試框架.Silverlight Unit Test Framework採用的最爲普遍. 但SUTf依然存在不少限制和須要改善的地方。下篇將介紹經過其餘第三方框架更簡潔實現UT.並總結相對SUTF具備優點和特色.

關於本盤若是任何問題 請在評論中指出.

本篇全部演示的源碼見附件。

參考資料:

Writing asynchronous unit tests with Rx and the Silverlight Unit Testing Framework

A Cheat Sheet for Unit Testing Silverlight Apps on Windows Phone 7

Asynchronous test support – Silverlight unit test framework and the UI thread
Running Windows Phone Unit Tests via MSBuild
相關文章
相關標籤/搜索