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

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

发布日期:2012年2月06日
作者:Leon 类别:Flash | 评论(6)

本文关注于宏观上的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

  1. http://ant.apache.org/下载ant1.8.2
  2. 解压到任意目录,设置环境变量ANT_HOME指向ant主目录
  3. 设置环境变量ANT_OPTS,值为-XX:MaxPermSize=128M -Xms128M -Xmx512M分配更大的内存空间

安装Hudson

  1. 首先下载hudson.war  http://www.hudson-ci.org/
  2. 其次通过 java -jar hudson.war命令运行
  3. 由于本身内置http服务,在浏览器中打开http://localhost:8080查看hudson是否运行
  4. 持续运行方法

简明使用方法

添加节点并进行设置

可以理解为一个项目,以下为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中通过以下指令指定:

<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中通过以下指令指定:

<taskdef resource="flexUnitTasks.tasks">
<classpath>  
<fileset dir="${lib.loc}">  
<include name="flexUnitTasks*.jar" />  
</fileset>  
</classpath>  
</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>中添加以下内容:

<define>  
<name>CONFIG::debug </name>  
<value>false </value>  
</define>  
<define>  
<name>CONFIG::embed </name>  
<value>false </value>  
</define>  
<define>  
<name>CONFIG::edit </name>  
<value>true </value>  
</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

框架原理图

框架原理

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

基础知识

testMethod,testCase,testSuite

结构图如下: 测试示意图

testMethod

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

  [Test ]
  public   function  testUpload ( ) { } ;

策略:

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

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

[Before ]
[Before   Class ]
[After ]
[After   Class ]
[Test ]

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

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

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

[Suite ]
[RunWith ( "org.flexunit.runners.Suite" ) ]
public   class  MultiFileUploaderSuite {
      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:

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

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

异步测试

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

事件处理示例代码:

[Test (async ,  timeout =5000 ) ]
public   function  testCancel ( ) : void {                         
      var  mHandler : Function   =  Async. asyncHandler (   this ,  cancelHandler ,
      5000 ,   {num : 4 ,type : "m" } ,  timeoutHandler   ) ;
 
    _uploader. addEventListener (  UploaderEvent. EVENT_FILE_ALL_MEMEORY ,
     mHandler   ) ;     
               
    _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呢,显然一目了然。

事件序列示例代码:

[Test (  async   ) ]
public   function  shouldCompleteTimerSequence ( ) : void   {
        var  timer :Timer   =   new  Timer (  TIMEOUT   ) ;
          var  sequence :SequenceRunner   =   new  SequenceRunner (   this   ) ;
        sequence. addStep (   new  SequenceCaller (  timer ,  timer. start   )   ) ;
        sequence. addStep (   new  SequenceWaiter (  timer ,  TimerEvent. TIMER ,  TIMEOUT2   )   ) ;
        sequence. addStep (   new  SequenceWaiter (  timer ,  TimerEvent. TIMER ,  TIMEOUT2   )   ) ;
        sequence. addStep (   new  SequenceWaiter (  timer ,  TimerEvent. TIMER ,  TIMEOUT2   )   ) ;
        sequence. addStep (   new  SequenceCaller (  timer ,  timer. stop   ) ;
        sequence. addAssertHandler (  handleSequenceComplete ,   null   ) ;
        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有:

  • IgnoredClassRunner
  • Suite
  • TheoryBlockRunner
  • SuiteMethod
  • FlexUnit1ClassRunner
  • Fluint1ClassRunner
  • BlockFlexUnit4ClassRunner
  • ParentRunner

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

import  org. flexunit. runner. IDescription ;
import  org. flexunit. runner. IRunner ;
import  org. flexunit. runner. notification. IRunNotifier ;
import  org. flexunit. token. IAsyncTestToken ;
public   class  CustomRunner implements IRunner   {
public   function  CustomRunner ( )   { }
public   function  run (notifier :IRunNotifier ,
        previousToken :IAsyncTestToken ) : void   { }
public   function  get description ( ) :IDescription   { }
public   function  pleaseStop ( ) : void   { }
}

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

UIListener

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

import  sampleSuite. SampleSuite ;
import  org. flexunit. listeners. UIListener ;
import  org. flexunit. runner. FlexUnitCore ;
private   var  core :FlexUnitCore ;
public   function  runMe ( ) : void   {
    core   =   new  FlexUnitCore ( ) ;
    core. addListener (   new  UIListener ( )   ) ;
    core. run (  sampleSuite. SampleSuite   ) ;
}

CIListener

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

core. addListener ( new  CIListener ( ) ) ;
core. run (  sampleSuite. SampleSuite   ) }

Mockolate

原理

对象的一个代理对象,包含原对象的所有公共方法和属性,mock对象的父类为原对象类,这样就可以在使用原对象的地方使用mock对象。

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

使用

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

[RunWith ( "mockolate.runner.MockolateRunner" ) ]
public   class  TestUploader {     
                     
[Rule ]
public   var  mocks :MockolateRule   =   new  MockolateRule ( ) ;
 
[Mock (type = "strict" ) ]
public   var  fileReference :FileReference ;
[Mock (type = "strict" ) ]
public   var  fileReferenceList :FileReferenceList ;     
 
}

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

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

var  frl :FileReferenceList   =  strict (  FileReferenceList   ) ;                                                             
mock (  frl   ). getter (   "fileList" ). returns (   [ ]   ) ;     
mock (  frl   ). method ( "toString" ). returns (   "FileReferenceList"   ) ;
mock (  frl   ). method (   "browse"   ). args (  Array   ). dispatches (   new  Event (  Event. SELECT ,   false ,   false   )   ) ;     
 
_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:

testUploadedToMemory ( )
testUploadedALLComplete ( )
testUploadedALLError ( )
testCancel ( )
testResume ( )
testDelete ( )

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

testCancel()的示例代码如下:

/**
 * 取消上传,上传完成2个后,cancel,验证以上传的ID列表的值是否为2

*/
        
[Test (async ,  order =4 ) ]
public   function  testCancel ( ) : void {
    _count   =   0 ;
    mockFileReference ( ) ;
    mockAMFPHP (   true   ) ;         
      var  mHandler : Function   =  Async. asyncHandler (   this ,  cancelHandler ,   5000 ,   {num : 4 ,type : "m" } ,
                                                                                timeoutHandler   ) ;
    _uploader. addEventListener (  UploaderEvent. EVENT_FILE_ALL_MEMEORY ,  mHandler   ) ;
    _uploader. addFileType (   "test"   ) ;
    _uploader. browse ( ) ;     
}

FileReferenceHeFileReferenceList的mock代码:

/**
 * mock reference相关的类 
 * 
 */
        
private   function  mockFileReference ( ) : void {             
      var  frl :FileReferenceList   =  strict (  FileReferenceList   ) ;                         
      var  num :int   =   4 ;
      var  arr :Array   =   [ ] ;
      for (   var  i :int ;  i   &lt ;  num ;  i ++   ) {
          var  fr :FileReference   =  strict (  FileReference   ) ;                     
        mock (  fr   ). getter (   &quot ;data &quot ;   ). returns (  _ba   ) ;
        mock (  fr   ). getter (   &quot ;type &quot ;   ). returns (   &quot ;. jpg &quot ;   ) ;
        mock (  fr   ). getter (   &quot ;size &quot ;   ). returns (  10000   ) ;
        mock (  fr   ). getter (   &quot ;name &quot ;   ). returns (   &quot ;fr &quot ;   ) ;
        mock (  fr   ). method (   &quot ;toString &quot ;   ). returns (   &quot ;FileReference &quot ;   ) ;                 
        mock (  fr   ). method ( &quot ;load &quot ; )                 
                       . dispatches (   new  Event (  Event. COMPLETE ,   false , false   )   )
           . dispatches (   new  IOErrorEvent (  IOErrorEvent. IO_ERROR ,   false ,   false   )   ) ;
        arr. push (  fr   ) ;                 
      }                         
                         
    mock (  frl   ). getter (   &quot ;fileList &quot ; ). returns (  arr   ) ;     
    mock (  frl   ). method ( &quot ;toString &quot ; ). returns (   &quot ;FileReferenceList &quot ;   ) ;
    mock (  frl   ). method (   &quot ;browse &quot ;   ). args (  Array   )
        . dispatches (   new  Event (  Event. SELECT ,   false ,   false   )   ) ;     
    _uploader. fileReferenceList   =  frl ;
}

事件处理cancelHandler方法如下:

/**
  * 上传过程中中断处理
  *  
  * @param e
  * @param pd
  * 
  */
        
private   function  cancelHandler (e :UploaderEvent ,  pd :Object ) : void {
      var  sHandler : Function ;
      switch (  pd. type   ) {
          case   "m" :           
            sHandler   =  Async. asyncHandler (   this ,  cancelHandler ,   5000 ,   {num : 4 ,  type : "a" } ,  timeoutHandler   ) ;                                             
            _uploader. addEventListener (  UploaderEvent. EVENT_FILE_COMPLETE ,  sHandler   ) ;
            _uploader. upload ( ) ;
              break ;
          case   "a" :     
              if (   ++_count   ==  2   ) {
                _uploader. cancel ( ) ;                         
              Assert. assertEquals (   "cancel失效:" ,  2 ,  _uploader. uploadedIDAll. length   ) ;
              }                                                                                 
      }             
}

如上所示:

调用_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失效: ”

参考资料

这篇文章有6个评论

 

 

 

文章出处:http://www.baiduux.com/blog/2012/02/06/flash-ci-and-testing/?tn=gongxinjun.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值