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
相关文章
相关标签/搜索