- 软件构建自动化
- 持续自动的构建检查
- 持续自动的构建测试(本篇文章的重点所在)
- 构建生成后续过程的自动化
- 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 (要加.表示当前路径)
- 从http://ant.apache.org/下载ant1.8.2
- 解压到任意目录,设置环境变量ANT_HOME指向ant主目录
- 设置环境变量ANT_OPTS,值为-XX:MaxPermSize=128M -Xms128M -Xmx512M分配更大的内存空间
- 首先下载hudson.war http://www.hudson-ci.org/
- 其次通过 java -jar hudson.war命令运行
- 由于本身内置http服务,在浏览器中打开http://localhost:8080查看hudson是否运行
- 持续运行方法
<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>"
<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>
- 在项目上点击右键,添加test case 或test suite(为case套装,包含n个case),添加相应的文件,flash builder会为你自动创建测试用主类FlexUnitApplication.as
- 在项目上点击右键“执行单元测试”或通过单元测试面板执行,选择相应的test case 或者test suite运行即可。
- 一般的结构如下,把AM2Test作为唯一的入口即可:
[Test]
public function testUpload(){};
public function testUpload(){};
- 一般为要至少为类中的每个方法写一个testMethod
- 对于一个方法,由于不同的条件逻辑可能会产生不同的结果,针对每一个结果写一个testMethod
- 针对没一个方法,写两个testMethod,针对有效和无效的输入
[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(){};
[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 );
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();
}
- 开始一个定时器
- 循环执行三次
- 停止定时器
- 执行断言
-BeforeClasses
-Rules
-Befores
-Test
-Afters
-Rules (the same ones as above)
-AfterClasses
-Rules
-Befores
-Test
-Afters
-Rules (the same ones as above)
-AfterClasses
- 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 {}
}
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 );
}
core.addListener(new CIListener());
core.run( sampleSuite.SampleSuite )}
core.run( sampleSuite.SampleSuite )}
[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;
- 针对接口,但不针对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