Flash持续集成自动化单元测试
发布日期:2012年2月06日持续集成(CI)
- 软件构建自动化
- 持续自动的构建检查
- 持续自动的构建测试(本篇文章的重点所在)
- 构建生成后续过程的自动化
hudson及搭建
安装JDK
- 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是否运行
- 持续运行方法
简明使用方法
添加节点并进行设置
添加job并进行设置
集成flexunit/pmd/cpd
自动化编译设置
<taskdef resource="flexTasks.tasks" classpath="${FLEX_HOME}/ant/lib/flexTasks.jar" />
单元测试所需文件设置
<taskdef resource="flexUnitTasks.tasks">
<classpath>
<fileset dir="${lib.loc}">
<include name="flexUnitTasks*.jar" />
</fileset>
</classpath>
</taskdef>"
<classpath>
<fileset dir="${lib.loc}">
<include name="flexUnitTasks*.jar" />
</fileset>
</classpath>
</taskdef>"
PMD/CPD
条件编译参数的方法
<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>
<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>
构建
windows下默认端口1024被占用问题
单元测试
flexunit4
进化史
框架原理图
使用方法
- 在项目上点击右键,添加test case 或test suite(为case套装,包含n个case),添加相应的文件,flash builder会为你自动创建测试用主类FlexUnitApplication.as
- 在项目上点击右键“执行单元测试”或通过单元测试面板执行,选择相应的test case 或者test suite运行即可。
- 一般的结构如下,把AM2Test作为唯一的入口即可:
基础知识
testMethod,testCase,testSuite
testMethod
[Test
]
public function testUpload ( ) { } ;
public function testUpload ( ) { } ;
- 一般为要至少为类中的每个方法写一个testMethod
- 对于一个方法,由于不同的条件逻辑可能会产生不同的结果,针对每一个结果写一个testMethod
- 针对没一个方法,写两个testMethod,针对有效和无效的输入
TestCase
[Before
]
[Before Class ]
[After ]
[After Class ]
[Test ]
[Before Class ]
[After ]
[After Class ]
[Test ]
[Test
(order
=1
)
]
public function testOrder1 ( ) { } ;
[Test (order =2 ) ]
public function testOrder2 ( ) { } ;
public function testOrder1 ( ) { } ;
[Test (order =2 ) ]
public function testOrder2 ( ) { } ;
TestSuite
[Suite
]
[RunWith ( "org.flexunit.runners.Suite" ) ]
public class MultiFileUploaderSuite {
public var _testUploadList :TestUploadList ;
}
[RunWith ( "org.flexunit.runners.Suite" ) ]
public class MultiFileUploaderSuite {
public var _testUploadList :TestUploadList ;
}
断言
assert
(
"错误提示"
,
预期值, 实际值
)
;
result
=
1
+
2
;
assertEqual ( "结果不为3" , 3 , result ) ;
assertEqual ( "结果不为3" , 3 , result ) ;
hamcrest
assertThat
(
value
,
matcher
)
;
assertThat
(
num1
,
is
(
between
(
num2
,
num3
)
)
)
;
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 { } ;
}
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 { } ;
}
异步测试
[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 ( ) ;
}
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毫秒内测试未完成,默认超时处理
- this:针对哪个TestCase,这里即为this
- cancelHandler,接收到UploaderEvent.EVENT_FILE_ALL_MEMORY事件后触发的事件对象
- 5000,超时处理触发的最大时间
- {num:4,type:”m”},传递给cancelHandler对象的参数
- timeoutHandler,超时处理对象,5000ms后未触发cancelHandler对象触发
[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 ( ) ;
}
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
-BeforeClasses
-Rules
-Befores
-Test
-Afters
-Rules (the same ones as above)
-AfterClasses
-Rules
-Befores
-Test
-Afters
-Rules (the same ones as above)
-AfterClasses
UIImpersonator
Runner和自定义Runner
- IgnoredClassRunner
- Suite
- TheoryBlockRunner
- SuiteMethod
- FlexUnit1ClassRunner
- Fluint1ClassRunner
- BlockFlexUnit4ClassRunner
- ParentRunner
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 { }
}
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
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 ) ;
}
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
core.
addListener
(
new
CIListener
(
)
)
;
core. run ( sampleSuite. SampleSuite ) }
core. run ( sampleSuite. SampleSuite ) }
Mockolate
原理
使用
[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 ;
}
public class TestUploader {
[Rule ]
public var mocks :MockolateRule = new MockolateRule ( ) ;
[Mock (type = "strict" ) ]
public var fileReference :FileReference ;
[Mock (type = "strict" ) ]
public var fileReferenceList :FileReferenceList ;
}
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 ;
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 ;
Case选择策略
- 针对接口,但不针对getter/setter方法
- 核心功能点,比如上传模块中的上传、中断等功能
- 逻辑复杂的功能点,if-else、switch-case等
- 针对功能点,可能组合各函数,在时间有限的情况下,非核心功能点可以省去
- 根据bug书写case,复现步骤,并验证之
- 不针对样式和展现等相关的功能点,例如,“当文件名为20个中文的时候,上传列表显示错乱”这样的问题,应完全由QA保证,属于非单测范围。
- 在单测开始之前,开发人员应和QA沟通确认哪些case是由QA保证,哪些case是由开发人员单测保证
示例展示
testUploadedToMemory
(
)
testUploadedALLComplete ( )
testUploadedALLError ( )
testCancel ( )
testResume ( )
testDelete ( )
testUploadedALLComplete ( )
testUploadedALLError ( )
testCancel ( )
testResume ( )
testDelete ( )
/**
* 取消上传,上传完成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 ( ) ;
}
* 取消上传,上传完成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 ( ) ;
}
/**
* mock reference相关的类
*
*/
private function mockFileReference ( ) : void {
var frl :FileReferenceList = strict ( FileReferenceList ) ;
var num :int = 4 ;
var arr :Array = [ ] ;
for ( var i :int ; i < ; num ; i ++ ) {
var fr :FileReference = strict ( FileReference ) ;
mock ( fr ). getter ( " ;data " ; ). returns ( _ba ) ;
mock ( fr ). getter ( " ;type " ; ). returns ( " ;. jpg " ; ) ;
mock ( fr ). getter ( " ;size " ; ). returns ( 10000 ) ;
mock ( fr ). getter ( " ;name " ; ). returns ( " ;fr " ; ) ;
mock ( fr ). method ( " ;toString " ; ). returns ( " ;FileReference " ; ) ;
mock ( fr ). method ( " ;load " ; )
. dispatches ( new Event ( Event. COMPLETE , false , false ) )
. dispatches ( new IOErrorEvent ( IOErrorEvent. IO_ERROR , false , false ) ) ;
arr. push ( fr ) ;
}
mock ( frl ). getter ( " ;fileList " ; ). returns ( arr ) ;
mock ( frl ). method ( " ;toString " ; ). returns ( " ;FileReferenceList " ; ) ;
mock ( frl ). method ( " ;browse " ; ). args ( Array )
. dispatches ( new Event ( Event. SELECT , false , false ) ) ;
_uploader. fileReferenceList = frl ;
}
* mock reference相关的类
*
*/
private function mockFileReference ( ) : void {
var frl :FileReferenceList = strict ( FileReferenceList ) ;
var num :int = 4 ;
var arr :Array = [ ] ;
for ( var i :int ; i < ; num ; i ++ ) {
var fr :FileReference = strict ( FileReference ) ;
mock ( fr ). getter ( " ;data " ; ). returns ( _ba ) ;
mock ( fr ). getter ( " ;type " ; ). returns ( " ;. jpg " ; ) ;
mock ( fr ). getter ( " ;size " ; ). returns ( 10000 ) ;
mock ( fr ). getter ( " ;name " ; ). returns ( " ;fr " ; ) ;
mock ( fr ). method ( " ;toString " ; ). returns ( " ;FileReference " ; ) ;
mock ( fr ). method ( " ;load " ; )
. dispatches ( new Event ( Event. COMPLETE , false , false ) )
. dispatches ( new IOErrorEvent ( IOErrorEvent. IO_ERROR , false , false ) ) ;
arr. push ( fr ) ;
}
mock ( frl ). getter ( " ;fileList " ; ). returns ( arr ) ;
mock ( frl ). method ( " ;toString " ; ). returns ( " ;FileReferenceList " ; ) ;
mock ( frl ). method ( " ;browse " ; ). args ( Array )
. dispatches ( new Event ( Event. SELECT , false , false ) ) ;
_uploader. fileReferenceList = frl ;
}
/**
* 上传过程中中断处理
*
* @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 ) ;
}
}
}
* 上传过程中中断处理
*
* @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 ) ;
}
}
}
参考资料
- 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一下-_-
这篇文章有6个评论
文章出处:http://www.baiduux.com/blog/2012/02/06/flash-ci-and-testing/?tn=gongxinjun.com