本文關注於宏觀上的CI和單元測試技術,某些技術上的具體細節會略過,更多細節請參考最後部分的「參考資料」及文中的連接。php
做者:杜明坦html
本文包括:持續集成、單元測試、Mock技術、Case選取策略和示例等五部分java
持續集成(CI)
CI是一種實踐,旨在緩和和穩固軟件的構建過程,可以應對以下挑戰:git
詳情見:
http://www.javaworld.com/javaworld/jw-12-2008/jw-12-hudson-ci.htmlgithub
hudson及搭建
一個很是流行的CI工具,易於安裝及配置,學習成本低,本篇中hudson安裝及配置在windows平臺進行。apache
安裝JDKwindows
從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瀏覽器
節點設置,主要設置」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
框架原理圖
使用方法
在項目上點擊右鍵,添加test case 或test suite(爲case套裝,包含n個case),添加相應的文件,flash builder會爲你自動建立測試用主類FlexUnitApplication.as
在項目上點擊右鍵「執行單元測試」或經過單元測試面板執行,選擇相應的test case 或者test suite運行便可。
通常的結構以下,把AM2Test做爲惟一的入口便可:
基礎知識
testMethod,testCase,testSuite
結構圖以下:
testMethod
最小的測試單元,顧名思義,針對類的方法的測試,使用[Test]元標籤,示例以下:
- [Test]
- public function testUpload(){};
策略:
通常爲要至少爲類中的每一個方法寫一個testMethod
對於一個方法,因爲不一樣的條件邏輯可能會產生不一樣的結果,針對每個結果寫一個testMethod
針對沒一個方法,寫兩個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。當測試單元運行的時候,Cla***equest對象會根據每一個Test Case和Test Suite的內容進行包裝成爲IRequest對象,併爲此對象構建相應的Runner,FlexUnitCore.run( Cla***equest)會執行全部的IRequest對象,觸發IRequest的run()方法,執行相應的Runner.run()。系統自定義的Runner有:
- IgnoredCla***unner
- Suite
- TheoryBlockRunner
- SuiteMethod
- FlexUnit1Cla***unner
- Fluint1Cla***unner
- BlockFlexUnit4Cla***unner
- 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 < 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;
- }
事件處理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失效: 」
參考資料
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一下-_-