求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
Flash持续集成自动化单元测试
 
作者: leon ,发布于2012-4-1
 

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

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

持续集成(CI)

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

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

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://http://www.hudson-ci.org/

2.其次通过 java -jar hudson.war命令运行

3.由于本身内置http服务,在浏览器中打开http://localhost:8080查看hudson是否运行

4.持续运行方法

简明使用方法

添加节点并进行设置

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

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

添加job并进行设置

可以理解为创建项目的一个任务,例子为auto,使用下图选项即可:

集成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”即可看到相应结果

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 {};

}

异步测试

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对象触发

事件序列,是一个很实用的使用格式,例如测试一个文件上传过程中取消的功能,会触发的事件有上传,取消成功,取消失败等,这个时候假如用事件处理的格式书写的话,会涉及到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();

}

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

开始一个定时器

循环执行三次

停止定时器

执行断言

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

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 {}

}

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


相关文章

微服务测试之单元测试
一篇图文带你了解白盒测试用例设计方法
全面的质量保障体系之回归测试策略
人工智能自动化测试探索
相关文档

自动化接口测试实践之路
jenkins持续集成测试
性能测试诊断分析与优化
性能测试实例
相关课程

持续集成测试最佳实践
自动化测试体系建设与最佳实践
测试架构的构建与应用实践
DevOps时代的测试技术与最佳实践


LoadRunner性能测试基础
软件测试结果分析和质量报告
面向对象软件测试技术研究
设计测试用例的四条原则
功能测试中故障模型的建立
性能测试综述
更多...   


性能测试方法与技术
测试过程与团队管理
LoadRunner进行性能测试
WEB应用的软件测试
手机软件测试
白盒测试方法与技术


某博彩行业 数据库自动化测试
IT服务商 Web安全测试
IT服务商 自动化测试框架
海航股份 单元测试、重构
测试需求分析与测试用例分析
互联网web测试方法与实践
基于Selenium的Web自动化测试
更多...