Flash持续集成自动化单元测试

简介:

本文关注于宏观上的CI和单元测试技术,某些技术上的具体细节会略过,更多细节请参考最后部分的“参考资料”及文中的链接。

作者:杜明坦

本文包括:持续集成、单元测试、Mock技术、Case选取策略和示例等五部分

持续集成(CI)
CI是一种实践,旨在缓和和稳固软件的构建过程,能够应对如下挑战:

  • 软件构建自动化
  • 持续自动的构建检查
  • 持续自动的构建测试(本篇文章的重点所在)
  • 构建生成后续过程的自动化
  • 持续集成示意图

详情见:
http://www.javaworld.com/javaworld/jw-12-2008/jw-12-hudson-ci.html


hudson及搭建
一个非常流行的CI工具,易于安装及配置,学习成本低,本篇中hudson安装及配置在windows平台进行。

安装JDK

从www.java.com下载直接安装,然后设置java相关的环境变量:

JAVA_HOME:jdk安装目录,例如:c:\java\jdk1.7.0
PATH:使得系统在任何路径下可以识别java命令,设为:%JAVA_HOME%/bin;%JAVA_HOME%/jre/bin
CLASSPATH:为java加载类(class or lib)路径,只有类在classpath中,java命令才能识别,设为:.;%JAVA_HOME%/lib/dt.jar;%JAVA_HOME%/lib/tools.jar (要加.表示当前路径)
安装ANT
从http://ant.apache.org/下载ant1.8.2
解压到任意目录,设置环境变量ANT_HOME指向ant主目录
设置环境变量ANT_OPTS,值为-XX:MaxPermSize=128M -Xms128M -Xmx512M分配更大的内存空间
安装Hudson
首先下载hudson.war http://www.hudson-ci.org/
其次通过 java -jar hudson.war命令运行
由于本身内置http服务,在浏览器中打开http://localhost:8080查看hudson是否运行
持续运行方法
简明使用方法
添加节点并进行设置

可以理解为一个项目,以下为admaker为例。 需要更多节点,请点击”New Node

Hudson新建节点 

节点设置,主要设置”Remote FS root”和“Launch Method”即可。

节点设置

添加job并进行设置
可以理解为创建项目的一个任务,例子为auto,使用下图选项即可:

Job设置

集成flexunit/pmd/cpd
hudnson通过ant进行自动化编译,把编译的结果设置为hudson的读取源,运行原理如下:

运行原理 

自动化编译设置

拷贝sdk\x.x.x\ant\lib下的flexTasks.jar至ant\lib目录下,并在build.xml中通过以下指令指定:

 
  1. <taskdef resource="flexTasks.tasks" classpath="${FLEX_HOME}/ant/lib/flexTasks.jar" /> 


单元测试所需文件设置

从https://github.com/flexunit/flexunit下载源代码,通过ant进行自动化编译,生成类似flexUnitTasks-4.1.0-x.jar的文件,拷贝到 项目所在的libs文件夹,并在build.xml中通过以下指令指定:

 
  1. <taskdef resource="flexUnitTasks.tasks"> 
  2. <classpath>  
  3. <fileset dir="${lib.loc}">  
  4. <include name="flexUnitTasks*.jar" />  
  5. </fileset>  
  6. </classpath>  
  7. </taskdef>


PMD/CPD

flexpmd,主要用来提升Flex/AS3源文件中的代码质量并且检测常见的不好的代码实践,比如无用的代码,效率低的代码片段,过于复杂的代码等等这些都是FlexPMD检测并报告的对象。

flexcpd,Flex/as3源文件中的代码重复率检查工具。 下载利用ant编译所需要的文件,最新版为1.2:http://opensource.adobe.com/wiki/display/flexpmd/Downloads

配置(flexpmd):http://opensource.adobe.com/wiki/display/flexpmd/How+to+invoke+FlexPMD

配置(flexcpd):http://opensource.adobe.com/wiki/display/flexpmd/FlexCPD

条件编译参数的方法

在flex-config.xml文件中的<compile></compile>中添加以下内容:

 
  1. <define>  
  2. <name>CONFIG::debug</name>  
  3. <value>false</value>  
  4. </define>  
  5. <define>  
  6. <name>CONFIG::embed</name>  
  7. <value>false</value>  
  8. </define>  
  9. <define>  
  10. <name>CONFIG::edit</name>  
  11. <value>true</value>  
  12. </define> 


构建
点击“Build Now”即可看到相应结果

Build结果

 

windows下默认端口1024被占用问题
中的属性port是指定socket server的端口号,而CIListener中的默认端口号是1024,此时需要做的是:

修改CIListener.as和VisualDebuggerListener.mxml中的默认端口号为9900,在flexunit项目目录中执行ant编译,在FlexUnit4Test\libs中找到flexunit-cilistener-41.0-1-4.5.0.0.swc文件,放到你的项目相应的地方即可,此时要注意显示指定port=”9900″。

为了不显示指定端口号9900,需在TestRunConfiguration.java中修改默认端口号为9900,在flex项目目录中执行ant编译,在FlexUnit4Test\libs\build中找到flexUnitTasks-4.1.0-1.jar文件,放到你的项目的相应的位置即可。 建议把完成以上两个步骤的操作

单元测试
flexunit4
进化史

as2unit 2003

flex1.0 flexunit

flex2.0 flexunit.9 as3—> as3flexunit(google code)—–>back to adobe

dpUInt—〉Fluint

flexunit4 2009

框架原理图

框架原理

 

使用方法

在项目上点击右键,添加test case 或test suite(为case套装,包含n个case),添加相应的文件,flash builder会为你自动创建测试用主类FlexUnitApplication.as
在项目上点击右键“执行单元测试”或通过单元测试面板执行,选择相应的test case 或者test suite运行即可。
一般的结构如下,把AM2Test作为唯一的入口即可:

基础知识
testMethod,testCase,testSuite

结构图如下:

测试示意图

testMethod

最小的测试单元,顾名思义,针对类的方法的测试,使用[Test]元标签,示例如下:

 
  1. [Test] 
  2. public function testUpload(){}; 


策略:

一般为要至少为类中的每个方法写一个testMethod
对于一个方法,由于不同的条件逻辑可能会产生不同的结果,针对每一个结果写一个testMethod
针对没一个方法,写两个testMethod,针对有效和无效的输入
TestCase

testMethod的集合,测试多个相关的功能点,一般针对某一个类,其中包含所有的需要测试的testMethod,包含如下特有的元数据标签,可以重复书写:

 
  1. [Before] 
  2. [Before Class] 
  3. [After] 
  4. [After Class] 
  5. [Test] 

其中每个[Test]是可以有顺序的,通过order属性指定,形如:

 

 
  1. [Test(order=1)] 
  2. public function testOrder1(){}; 
  3. [Test(order=2)] 
  4. public function testOrder2(){}; 
  5. TestSuite 

testCase的集合,使用如下元数据标签标记,示例如下:

 
  1. [Suite] 
  2. [RunWith("org.flexunit.runners.Suite")] 
  3. public class MultiFileUploaderSuite{ 
  4. public var _testUploadList:TestUploadList; 

断言
断言用于比较测试目标的结果和预期值,一般格式如下:

assert( "错误提示", 预期值, 实际值 );
示例:

result = 1 + 2;
assertEqual( "结果不为3", 3, result );
有两种使用格式:

hamcrest
开源的匹配器库,格式如下:

assertThat( value, matcher );
其中,matcher是一个类,有is(),between(),closeTo()等

用法举例:

assertThat( num1, is( between( num2, num3 ) ) );
自定义matcher:

 
  1. import org.hamcrest.TypeSafeMatcher; 
  2. import org.hamcrest.Description; 
  3. public class myMatcher exends TypeSafeMatcher{ 
  4. public function myMatcher(){}; 
  5. override public function matchesSafely(item:Object):Boolean {}; 
  6. override public function describeTo(description:Description):void {}; 

更多内容见:http://github.com/drewbourne/hamcrest-as3

异步测试
flexunit4的核心功能,想想flash中的各种异步事件吧!包含两种格式,事件处理和事件序列(sequence)。

事件处理示例代码:

 
  1. [Test(async, timeout=5000)] 
  2. public function testCancel():void{  
  3. var mHandler:Function = Async.asyncHandler( this, cancelHandler, 
  4. 5000, {num:4,type:"m"}, timeoutHandler ); 
  5.  
  6. _uploader.addEventListener( UploaderEvent.EVENT_FILE_ALL_MEMEORY, 
  7. mHandler );  
  8.  
  9. _uploader.loadtoMemory();  

如示例所示:

async:必须存在,说明是异步测试
timeout=5000, 在5000毫秒内测试未完成,默认超时处理
mHandle是异步处理对象,包含5个参数,意义如下:

this:针对哪个TestCase,这里即为this
cancelHandler,接收到UploaderEvent.EVENT_FILE_ALL_MEMORY事件后触发的事件对象
5000,超时处理触发的最大时间
{num:4,type:”m”},传递给cancelHandler对象的参数
timeoutHandler,超时处理对象,5000ms后未触发cancelHandler对象触发
更多内容见:更多内容见:http://docs.flexunit.org/index.php?title=Writing_an_Async_setup#Approach

事件序列,是一个很实用的使用格式,例如测试一个文件上传过程中取消的功能,会触发的事件有上传,取消成功,取消失败等,这个时候假如用事件处理的格式书写的话,会涉及到n个处理函数,很复杂,而用sequence呢,显然一目了然。

事件序列示例代码:

 
  1. [Test( async )] 
  2. public function shouldCompleteTimerSequence():void { 
  3. var timer:Timer = new Timer( TIMEOUT ); 
  4. var sequence:SequenceRunner = new SequenceRunner( this ); 
  5. sequence.addStep( new SequenceCaller( timer, timer.start ) ); 
  6. sequence.addStep( new SequenceWaiter( timer, TimerEvent.TIMER, TIMEOUT2 ) ); 
  7. sequence.addStep( new SequenceWaiter( timer, TimerEvent.TIMER, TIMEOUT2 ) ); 
  8. sequence.addStep( new SequenceWaiter( timer, TimerEvent.TIMER, TIMEOUT2 ) ); 
  9. sequence.addStep( new SequenceCaller( timer, timer.stop ); 
  10. sequence.addAssertHandler( handleSequenceComplete, null ); 
  11. sequence.run(); 

如示例所示,执行步骤如下:

开始一个定时器
循环执行三次
停止定时器
执行断言
更多内容见:更多内容见:http://docs.flexunit.org/index.php?title=Fluint_Sequences

Rules
类似于[Before]和[After]的元数据标签,提供更多高级功能,定义每个test运行前后的规则,在每个test之前和之后运行,结构如下:

-BeforeClasses
-Rules
-Befores
-Test
-Afters
-Rules (the same ones as above)
-AfterClasses
UIImpersonator
借鉴Fluint框架的测试内容的显示对象,通过它你可以把可视化的内容暂时在舞台上,其实个人认为实用性不大,和自动化的思想有违背。在纯as3项目中暂时无法显示,不过可以利用这个技巧实现:https://gist.github.com/1094408

更多内容见:http://docs.flexunit.org/index.php?title=UIImpersonator

Runner和自定义Runner
规定了Test Method、Test Case和Test Suite运行时的行为,兼容性好,能很好的运行flexunit1和fluint框架的测试内容。有不同责任的Runner,最常用的就是Suite。当测试单元运行的时候,ClassRequest对象会根据每个Test Case和Test Suite的内容进行包装成为IRequest对象,并为此对象构建相应的Runner,FlexUnitCore.run( ClassRequest)会执行所有的IRequest对象,触发IRequest的run()方法,执行相应的Runner.run()。系统自定义的Runner有:

 
  1. IgnoredClassRunner 
  2. Suite 
  3. TheoryBlockRunner 
  4. SuiteMethod 
  5. FlexUnit1ClassRunner 
  6. Fluint1ClassRunner 
  7. BlockFlexUnit4ClassRunner 
  8. ParentRunner 

要自定义Runner,可以实现以下方法:

 
  1. import org.flexunit.runner.IDescription; 
  2. import org.flexunit.runner.IRunner; 
  3. import org.flexunit.runner.notification.IRunNotifier; 
  4. import org.flexunit.token.IAsyncTestToken; 
  5. public class CustomRunner implements IRunner { 
  6. public function CustomRunner() {} 
  7. public function run(notifier:IRunNotifier, 
  8. previousToken:IAsyncTestToken):void {} 
  9. public function get description():IDescription {} 
  10. public function pleaseStop():void {} 

更多内容见:http://docs.flexunit.org/index.php?title=Custom_Runners

UIListener
Runner的监听对象,对监听内容进行图形化表示,过程如下:

 
  1. import sampleSuite.SampleSuite; 
  2. import org.flexunit.listeners.UIListener; 
  3. import org.flexunit.runner.FlexUnitCore; 
  4. private var core:FlexUnitCore; 
  5. public function runMe():void { 
  6. core = new FlexUnitCore(); 
  7. core.addListener( new UIListener() ); 
  8. core.run( sampleSuite.SampleSuite ); 
  9. CIListener 

Runner的监听对象,不需要进行图形化的表示,说要做的就是把监听到的内容通过Socket发送给服务端,把内容写到磁盘,供Hudson读取处理,过程如下:

core.addListener(new CIListener());
core.run( sampleSuite.SampleSuite )}
Mockolate
原理
对象的一个代理对象,包含原对象的所有公共方法和属性,mock对象的父类为原对象类,这样就可以在使用原对象的地方使用mock对象。

由于mock技术是一门独立的技术,在此不作详述,请参考:http://www.mockolate.org/

使用
自定义Runner,自定义Rule,以及Mock标签:

 
  1. [RunWith("mockolate.runner.MockolateRunner")] 
  2. public class TestUploader{  
  3.  
  4. [Rule] 
  5. public var mocks:MockolateRule = new MockolateRule(); 
  6.  
  7. [Mock(type="strict")] 
  8. public var fileReference:FileReference; 
  9. [Mock(type="strict")] 
  10. public var fileReferenceList:FileReferenceList;  
  11.  

如上所示,在运行这个TestCase的时候使用MockolateRunner,自定义MockoateRule,通过Mock标签指定要mock的类,并指定为strict模式。

接下来做的是在TestMethod中使用它们:
 

 
  1. var frl:FileReferenceList = strict( FileReferenceList );  
  2. mock( frl ).getter( "fileList").returns( [] );  
  3. mock( frl ).method("toString").returns( "FileReferenceList" ); 
  4. mock( frl ).method( "browse" ).args( Array ).dispatches( new Event( Event.SELECT, false, false ) );  
  5.  
  6. _uploader.fileReferenceList = frl

如上所示,
getter方法fileList的返回为一个[],

toString()方法返回“FileFeferenceList”,

调用browse()方法时,指定类型为Array的参数,并派发SELECT事件。

最后,通过_uploader.fileReferenceList = fr1,把Mock对象传递给_uploader对象。

Case选择策略
针对接口,但不针对getter/setter方法
核心功能点,比如上传模块中的上传、中断等功能
逻辑复杂的功能点,if-else、switch-case等
针对功能点,可能组合各函数,在时间有限的情况下,非核心功能点可以省去
根据bug书写case,复现步骤,并验证之
不针对样式和展现等相关的功能点,例如,“当文件名为20个中文的时候,上传列表显示错乱”这样的问题,应完全由QA保证,属于非单测范围。
在单测开始之前,开发人员应和QA沟通确认哪些case是由QA保证,哪些case是由开发人员单测保证
示例展示
以多文件上传核心类Uploader为例,要想测试此类必须要使用mock技术,因为FileReference的data为只读属性

根据选取策略,最终锁定在来Uploader类的核心方法,并建立如下的case:

 
  1. testUploadedToMemory() 
  2. testUploadedALLComplete() 
  3. testUploadedALLError() 
  4. testCancel() 
  5. testResume() 
  6. testDelete() 

每个case都会对FileReference、FileReferenceList和用于和AMF后端通讯的核心类AMFPHP进行mock,

testCancel()的示例代码如下:
 

 
  1. /** 
  2. * 取消上传,上传完成2个后,cancel,验证以上传的ID列表的值是否为2 
  3. *  
  4. */  
  5. [Test(async, order=4)] 
  6. public function testCancel():void{ 
  7. _count = 0
  8. mockFileReference(); 
  9. mockAMFPHP( true );  
  10. var mHandler:Function = Async.asyncHandler( this, cancelHandler, 5000, {num:4,type:"m"}, 
  11. timeoutHandler ); 
  12. _uploader.addEventListener( UploaderEvent.EVENT_FILE_ALL_MEMEORY, mHandler ); 
  13. _uploader.addFileType( "test" ); 
  14. _uploader.browse();  
  15.  
  16. FileReferenceHeFileReferenceList的mock代码: 
  17. /** 
  18. * mock reference相关的类  
  19. *  
  20. */  
  21. private function mockFileReference():void{  
  22. var frl:FileReferenceList = strict( FileReferenceList );  
  23. var num:int = 4
  24. var arr:Array = []; 
  25. for( var i:int; i &lt; num; i++ ){ 
  26. var fr:FileReference = strict( FileReference );  
  27. mock( fr ).getter( &quot;data&quot; ).returns( _ba ); 
  28. mock( fr ).getter( &quot;type&quot; ).returns( &quot;.jpg&quot; ); 
  29. mock( fr ).getter( &quot;size&quot; ).returns( 10000 ); 
  30. mock( fr ).getter( &quot;name&quot; ).returns( &quot;fr&quot; ); 
  31. mock( fr ).method( &quot;toString&quot; ).returns( &quot;FileReference&quot; );  
  32. mock( fr ).method(&quot;load&quot;)  
  33. .dispatches( new Event( Event.COMPLETE, false,false ) ) 
  34. .dispatches( new IOErrorEvent( IOErrorEvent.IO_ERROR, false, false ) ); 
  35. arr.push( fr );  
  36. }  
  37.  
  38. mock( frl ).getter( &quot;fileList&quot;).returns( arr );  
  39. mock( frl ).method(&quot;toString&quot;).returns( &quot;FileReferenceList&quot; ); 
  40. mock( frl ).method( &quot;browse&quot; ).args( Array ) 
  41. .dispatches( new Event( Event.SELECT, false, false ) );  
  42. _uploader.fileReferenceList = frl

事件处理cancelHandler方法如下:

 
  1. /** 
  2. * 上传过程中中断处理 
  3. *  
  4. * @param e 
  5. * @param pd 
  6. *  
  7. */  
  8. private function cancelHandler(e:UploaderEvent, pd:Object):void{ 
  9. var sHandler:Function; 
  10. switch( pd.type ){ 
  11. case "m":  
  12. sHandler = Async.asyncHandler( this, cancelHandler, 5000, {num:4, type:"a"}, timeoutHandler );  
  13. _uploader.addEventListener( UploaderEvent.EVENT_FILE_COMPLETE, sHandler ); 
  14. _uploader.upload(); 
  15. break; 
  16. case "a":  
  17. if( ++_count == 2 ){ 
  18. _uploader.cancel();  
  19. Assert.assertEquals( "cancel失效:", 2, _uploader.uploadedIDAll.length ); 
  20. }  
  21. }  


如上所示:

调用_uploader.browse(),

派发Event.SELECT事件

Uploader内部把文件内容加载到内存,派发UploaderEvent.EVENT_FILE_ALL_MEMEORY事件

在CancelHandler中处理UploaderEvent.EVENT_FILE_ALL_MEMEORY事件

假如pd的属性type为m,进行上传

通过_count变量判断上传了两次,调用_uploader.cancle()方法

进行Assert,判断上传成功的数组列表的长度是否为2

失败的话,输出”cancel失效: ”

参考资料

 

http://docs.flexunit.org/ flexunit 官方教程

http://tutorials.digitalprimates.net/index.htm  flexunit4.1完整教程

https://github.com/flexunit/flexunit flexunit4源代码

http://mockolate.org mockolate官方教程

http://hudson-ci.org/ hudson官网

http://www.unitedmindset.com/jonbcampos/2010/02/02/run-flex-unit-tests-from-ant/

http://flexunit.digitalprimates.net:8080/view/All/

http://blog.csdn.net/lixuekun820/article/details/5881647

http://www.flexonjava.net/2008/12/flex-3-unable-to-resolve-resource.html

http://macleo.iteye.com/blog/870004

http://stackoverflow.com/questions/3714957/address-already-in-use-jvm-bind

http://forums.adobe.com/community/opensource/flexunit?view=all 

https://gist.github.com/1094408 

      
更多资料,请baidu一下-_- 

 












本文转自百度技术51CTO博客,原文链接:http://blog.51cto.com/baidutech/773929 ,如需转载请自行联系原作者
相关文章
|
1月前
|
敏捷开发 运维 测试技术
探索自动化测试在持续集成环境中的关键作用
【2月更文挑战第18天】 随着敏捷开发和DevOps文化的普及,持续集成(CI)已成为软件开发过程中不可或缺的组成部分。本文将深入探讨自动化测试在持续集成环境中的重要性,分析其如何提高软件交付速度、保障质量并减少人工干预。通过对现代软件工程实践中自动化测试策略的剖析,揭示了其在维护高效率和高质量软件产品中的核心地位。
31 7
|
5天前
|
监控 测试技术 数据安全/隐私保护
如何将代理IP集成到自动化测试框架中?
如何将代理IP集成到自动化测试框架中?
|
9天前
|
测试技术 持续交付 Docker
Django中的自动化部署与持续集成实践
【4月更文挑战第15天】本文介绍了Django项目中自动化部署与持续集成的实践方法。自动化部署通过选择Ansible、Fabric或Docker等工具,编写部署脚本,配置持续集成工具(如Jenkins、GitLab CI),确保服务器环境一致,实现快速应用上线。持续集成则涉及配置版本控制系统,设置自动化构建和测试,编写全面的测试用例,集成代码质量检查工具,并配置通知机制,以提升代码质量和开发效率。这两者结合能有效提升项目的迭代速度和可靠性。
|
10天前
|
分布式计算 Hadoop 测试技术
Hadoop【基础知识 05】【HDFS的JavaAPI】(集成及测试)
【4月更文挑战第5天】Hadoop【基础知识 05】【HDFS的JavaAPI】(集成及测试)
38 8
|
29天前
|
存储 监控 测试技术
【软件设计师备考 专题 】系统集成测试的准备和执行
【软件设计师备考 专题 】系统集成测试的准备和执行
56 0
|
1月前
|
运维 监控 Devops
构建高效自动化运维体系:基于容器技术的持续集成与持续部署实践
在数字化转型的浪潮中,企业的IT基础设施和软件交付模式正经历着深刻的变革。传统的运维方式已难以满足快速迭代、灵活扩展的现代业务需求。本文将探讨如何通过容器技术实现高效的自动化运维体系,重点分析持续集成(CI)与持续部署(CD)的实践方法及其对企业运维效率的影响。通过引入微服务架构、容器编排、DevOps文化等概念,我们旨在为读者提供一套全面的自动化运维解决方案,以支持业务的敏捷性和可扩展性。
|
1月前
|
敏捷开发 监控 Devops
深入探究持续集成中的自动化测试策略
【2月更文挑战第29天】随着敏捷开发和DevOps文化的普及,持续集成(CI)已成为软件开发流程中不可或缺的一部分。CI流程的核心在于快速、频繁地集成代码更改,并确保这些更改不会破坏已有功能。本文将重点探讨在持续集成环境中实施自动化测试的策略,以及如何通过有效的测试实践来提高软件质量和交付速度。我们将分析不同的测试级别、测试类型以及它们如何整合到CI流程中,同时讨论如何优化测试过程以减少反馈周期时间,并确保高质量的构建。
|
1月前
|
数据采集 数据处理 开发工具
argparse是你的好帮手:快速编写自动化脚本、测试脚本、数据处理脚本
argparse是你的好帮手:快速编写自动化脚本、测试脚本、数据处理脚本
|
1月前
|
敏捷开发 Devops 测试技术
探索自动化测试在持续集成中的关键作用
【2月更文挑战第25天】 随着敏捷开发模式的普及,持续集成(CI)已成为软件开发流程不可或缺的一部分。本文将探讨自动化测试在持续集成环境中的重要性及其如何提升软件交付的速度和质量。通过分析自动化测试的优势、实施策略以及面临的挑战,我们旨在为读者提供深入理解,并指导实践中的应用。
11 3
|
1月前
|
敏捷开发 监控 数据管理
探索自动化测试在持续集成环境中的关键角色
【2月更文挑战第24天】 在当今软件开发的快节奏环境中,自动化测试不再是一个选择,而是确保产品质量和加速市场交付的必要条件。本文将深入探讨自动化测试在持续集成(CI)环境中的重要性,分析其如何提高测试效率、降低错误率,并最终促进开发流程的持续改进。我们将通过具体的案例研究和最佳实践,揭示自动化测试策略的关键要素,以及如何在CI流程中有效集成自动化测试。

热门文章

最新文章