Continuous Integration with Flex 2 (Actionscript 3), FlexUnit, CruiseControl, Apollo and subversion on OS X

April 24th, 2007

UPDATE: I’ve created a newer tutorial using CruiseControl.rb. IMO Its a better setup.

Here are the steps I went through to get CruiseControl working with Flash Actionscript 3 (AS3) and Flex 2.
Most of this information can be found around the internet (eyefodder tutorials). The main additions I’ve contributed are putting all the information in one place and using Apollo to run testcases and output the pass/fail data.
These steps are for OS X but should be close to the same for any other os that the Flex 2 sdk will run on. The main changes would be the svn server and client install and paths in the ant build file.

Overview
1. Install the Flex 2 SDK and the Apollo SDK
2. Create a simple Actionscript 3 app.
3. Install FlexUnit and create a test case.
4. Instal svn server
5. install svn client
6. Create an Apollo app that outputs unit test result xml for CruiseControl
7. Create build files for building the HelloWorld app and Apollo test app.
8. Add the project to svn
9. Install and setup CruiseControl

Install the Flex 2 SDK and the Apollo SDK

1. Download the Flex 2 SDK from here: http://www.adobe.com/products/flex/sdk/
2. Put the sdk here on your machine (create directories if needed): /Developer/adobe/sdk/flex_sdk_2
3. Download the Apollo SDK from here:http://labs.adobe.com/technologies/apollo/
4. The Apollo SDK is installed into the Flex 2 SDK. Move all of the files from the Apollo SDK directory to the flex_sdk_2 directory or follow adobe labs instructions here: Adobe Labs Apollo SDK set up

Create a simple Actionscript 3 app.
This is just a simple Hello world app with one test case.

1. Create a directory named “helloworld” in your root directory (”/helloworld”)
2. create a new text file in that directory called “HelloWorld.as”
3. Add this code to HelloWorld.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package
{
    import flash.display.MovieClip;
    import flash.text.TextField;
 
    public class HelloWorld extends MovieClip
    {
        public function HelloWorld()
        {
            var helloDisplay:TextField = new TextField();
            helloDisplay.text = getHelloWorldString();
            addChild(helloDisplay);
        }
 
        public function getHelloWorldString():String
        {
            return "Hello World";
        }
    }
}

Install FlexUnit and create a test case.

1. Download FlexUnit from here:http://code.google.com/p/as3flexunitlib/
2. From the FlexUnit directory copy this directory: FlexUnit/src/trunk/src/actionscript3/flexunit to the /helloworld directory.
3. Create a new text file in the /helloworld directory calld HelloWorldTest.as
4. Add this code to HelloWorldTest.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package
{
 	import flexunit.framework.TestCase;
 	import flexunit.framework.TestSuite;
 	import HelloWorld;
 
 	public class HelloWorldTest extends TestCase
 	{
 
  	    public function HelloWorldTest( methodName:String )
  	    {
   			super( methodName );
        }
 
  		public static function suite():TestSuite
  		{
   			var ts:TestSuite = new TestSuite();
   			ts.addTest( new HelloWorldTest( "testGetHelloWorldString" ) );
   			return ts;
   		}
 
   		public function testGetHelloWorldString():void
   		{
            var helloWorld:HelloWorld = new HelloWorld();
   			assertEquals("Hello Wsssorld", helloWorld.getHelloWorldString());
   		}
  	}
}

Install svn server

1. Download MAS (Mac SVN): http://sourceforge.net/projects/macsvnserver
MAS is a one click svn server similar MAMP.

2. Install MAS by dragging the app to your Applications folder.

3. Start MAS and create a new user. When it asks for the username use “user” and for the password use “pass” (you can change this later).

Install svn-client

1. To instal svn-client first download and install Fink: http://finkproject.org/

2. Once Fink is installed, use the included FinkCommander application to install svn-client. (select “svn-client” in the FinkCommander list then Binary->install)

Create an Apollo app that outputs unit test result xml for CruiseControl
1. Create a text file inside the helloworld directory called TestRunner.as
2. add this code to TestRunner.as (this has only minor changes to the TestRunner.as files included with FlexUnit)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package
{
	import flash.utils.*;
	import flexunit.framework.*;
	import flexunit.runner.*;
	import flash.filesystem.File;
	import mx.core.Application;
 
	public class TestRunner extends BaseTestRunner
	{
	    private var startTime			: int;
	    private var totalTestCount		: int;
	    private var numTestsRun			: int;
	    private var result				: TestResult;
	    private var testsComplete		: Function;
	    private var printer				: CruiseControlResultPrinter;
 
	    public function TestRunner(outFile:File, onComplete:Function=null)
	    {
	        printer = new CruiseControlResultPrinter();
	        printer.setOutput(outFile);
	        testsComplete = onComplete;
	    }
	//------------------------------------------------------------------------------
 
	    public static function run( test:Test, outFile:File, onComplete:Function=null ):TestResult
	    {
	        return new TestRunner(outFile, onComplete).doRun( test );
	    }
 
	//------------------------------------------------------------------------------
	    private function doRun( test:Test ):TestResult
	    {
	        result = new TestResult();
	        result.addListener(TestListener( printer ));
	        result.addListener(TestListener( this ));
 
	        startTime = getTimer();
	        totalTestCount = test.countTestCases();
	        numTestsRun = 0;
	        test.runWithResult( result );
	        return result;
	    }
	//------------------------------------------------------------------------------
	    override public function testEnded( test : Test ):void	
	    {
	        if (++numTestsRun == totalTestCount)
	        {
	            var endTime:Number = getTimer();
	            var runTime:Number = endTime - startTime;
	          	printer.writeFile();
	            if(testsComplete != null)
	            {
	                testsComplete();
	            }
	            Application.application.stage.window.close();
	        }
	    }
 
 
	}
 
 
 
}

3. Crate a text file called CruiseControlResultPrinter.as
4. Add this code to CruiseControlResultPrinter.as: (this code is a slightly modified version of this tutorial by eyefodder )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package
{
 
	import flash.utils.*;
	import flash.xml.XMLNode;
 
	import flexunit.framework.AssertionFailedError;
	import flexunit.framework.Test;
	import flexunit.framework.TestCase;
	import flexunit.framework.TestFailure;
	import flexunit.framework.TestListener;
	import flexunit.framework.TestResult;
	import flexunit.utils.Iterator;
	import flash.filesystem.*;
 
 
	public class CruiseControlResultPrinter implements TestListener
	{
 
 
		private var __resultXML		: XML;
		private var __testTimer		: int;
		private var __suiteSuccess	: Boolean = true;
		private var file			: File;
 
 
		function CruiseControlResultPrinter()
		{
			__resultXML = new XML("<testsuites />");
		}
 
	//------------------------------------------------------------------------------
	    public function print( result:TestResult, runTime:Number ):void
	    {
 
	        printHeader(runTime);
	        printMain();
	        printFooter(result);
 
	    }
	//------------------------------------------------------------------------------ 
	    public function writeFile():void
	    {
	        if(file != null)
			{
				var a:FileStream;
				var fileStream:FileStream = new FileStream();
				fileStream.open(file, FileMode.WRITE);
				fileStream.writeUTFBytes(__resultXML.toString());
 
			}
		}
 
	 //------------------------------------------------------------------------------ 
	    public function setOutput(file:File):void
		{
			this.file = file;
		}
 
	//------------------------------------------------------------------------------
		private function printMain():void{
			trace("Test Suite success: "+__suiteSuccess+"\n");
			trace(__resultXML);
		}
	//------------------------------------------------------------------------------
 
	    public function printHeader( runTime:Number ):void
	    {
	      trace("-----------------TESTRUNNEROUTPUTBEGINS----------------");
	    }
	//------------------------------------------------------------------------------
 
 
	    private function printFooter( result:TestResult ):void
	    {
 
	       trace("-----------------TESTRUNNEROUTPUTENDS----------------");
	    }
 
	//------------------------------------------------------------------------------
 
	    public function addError( test:Test, error:Error ):void
	    {
	      __suiteSuccess = false;
	      var testNode:XML = getTestNode(test);
	      var errorNode:XML = new XML("<error>< ![CDATA["+error.getStackTrace()+"]]></error>");
	      testNode.appendChild(errorNode);
	    }
 
	//------------------------------------------------------------------------------
 
	    public function addFailure( test:Test, error:AssertionFailedError ):void
	    {
	      __suiteSuccess = false;
	      var testNode:XML = getTestNode(test);
	      var failureNode:XML = new XML("<failure>< ![CDATA["+error.getStackTrace()+"]]></failure>");
	      testNode.appendChild(failureNode);
	    }
 
	//------------------------------------------------------------------------------
 
	    public function endTest( test:Test ):void
	    {
	    	var testNode:XML = getTestNode(test);
	    	testNode.@time = (getTimer() - __testTimer)/1000;
	    }
 
	//------------------------------------------------------------------------------
 
	    public function startTest( test:Test ):void
	    {
		   var suiteNode:XML = getSuiteNode(test)
		   suiteNode.appendChild(new XML("<testcase name=\""+TestCase(test).methodName+"\" time=\"\"/>"));
		   __testTimer = getTimer();
	    }
 
	//------------------------------------------------------------------------------
 
	    private function getSuiteNode (test:Test):XML
	    {
	    	var outNode:XML = __resultXML.testsuite.(@name==test.className)[0];
	    	if(outNode==null)
	    	{
	    		outNode = new XML("<testsuite name=\""+test.className+"\"/>");
	    		__resultXML.appendChild(outNode);
	    	}
	    	return outNode;
	    }
		private function getTestNode(test:Test):XML
		{
			return __resultXML.testsuite.testcase.(@name==TestCase(test).methodName)[0];
		}
 
	}
 
}

5. Create a text file in the helloworld directory called testXmlOut.mxml
6. Add this code to testXmlOut.mxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
< ?xml version="1.0" encoding="utf-8"?>
<mx :Application xmlns:mx="http://www.adobe.com/2006/mxml"  xmlns="*" xmlns:flexunit="flexunit.flexui.*" creationComplete="onCreationComplete()" width="0" height="0">
    </mx><mx :Script>
        < ![CDATA[
            import flash.filesystem.File;
            import flexunit.framework.TestSuite;
 
            public var testRunner   : TestRunner
            public var testSuite    : TestSuite;
 
            private function onCreationComplete():void
            {
                testSuite = createSuite();
                this.callLater(runTests);
            }
 
            private function createSuite():TestSuite 
            {
 				var ts:TestSuite = new TestSuite();
 				ts.addTest(HelloWorldTest.suite());
 				return ts;
 			}
 			public function runTests():void
 			{
 			    TestRunner.run(testSuite, new File(File.currentDirectory.nativePath+"/test.xml"));
 			}
		]]>
	</mx>

Create build files for building the HelloWorld app and Apollo test app.

1. Create a text file in the helloworld directory called build.xml
2. add this code to build.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<project name="helloworld" default="compile">
    <target name="compile">
       <exec dir="." failonerror="true" executable="/Developer/adobe/sdk/flex_sdk_2/bin/mxmlc">
            <arg line="HelloWorld.as -output helloworld.swf"/>
        </exec>
    	<exec dir="." failonerror="true" executable="/Developer/adobe/sdk/flex_sdk_2/bin/amxmlc">
    	   	<arg line="testXmlOut.mxml -output testXmlOut.swf" />
    	</exec>
 
    	<exec dir="." failonerror="true" executable="/Developer/adobe/sdk/flex_sdk_2/bin/adl">
			<arg line="testXmlOut-app.xml" />
		</exec>
    </target>
</project>

3. Create a text file in the helloworld directory called testXmlOut-app.xml
4. Add this code to testXmlOut-app.xml

1
2
3
4
5
6
7
8
9
10
11
< ?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://ns.adobe.com/apollo/application/1.0.M3" 
    appId="testXmlOut" version="1.0">
      <properties>
         <name>testXmlOut</name>
         <description>A test Apollo application.</description>
         <publisher>Aaron Spjut</publisher>
         <copyright>2007</copyright>
     </properties>
     <rootcontent systemChrome="none" transparent="true" visible="true">testXmlOut.swf</rootcontent>
</application>

Add the project to svn

1. open a terminal window and enter this commands:

svn import /helloworld http://localhost:8800/svn/helloworld --username user --password pass -m "initial import"

Install and setup CruiseControl

1. Download CruiseControl from here: http://cruisecontrol.sourceforge.net/download.html
2. Rename the unzipped directory to “cruisecontrol” and put it in your root directory (/cruisecontrol)
3. open /cruisecontrol/config.xml
3. remove the existing code and replace it with this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<cruisecontrol>
    <project name="helloworld">
        <listeners>
            <currentbuildstatuslistener file="logs/${project.name}/status.txt"/>
        </listeners>
        <bootstrappers>
            <svnbootstrapper username="user" password="pass" file="http://localhost:8800/svn/helloworld/build.xml"/>
        </bootstrappers>
 
        <modificationset quietperiod="10">
            <svn LocalWorkingCopy="projects/${project.name}" username="user" password="pass" RepositoryLocation="http://localhost:8800/svn/helloworld"/>
        </modificationset>
        <log>
            <merge file="projects/${project.name}/test.xml"/>
        </log>
        <schedule interval="300">
            <ant anthome="apache-ant-1.6.5" buildfile="projects/${project.name}/build.xml"/>
        </schedule>
    </project>
</cruisecontrol>

4. Checkout the project from svn into the CruiseControl projects folder by entering this command in a terminal window:

svn checkout http://localhost:8800/svn/helloworld /cruisecontrol/projects/helloworld --username user --password pass

5. Start cruise control by entering these commands:

JAVA_HOME=/Library/Java/Home; export JAVA_HOME
cd /cruisecontrol
./cruisecontrol.sh

6. open up a web browser and load this url: http://localhost:8080
You will see two projects: helloworld and connectfour. connectfour is an example that comes with CruiseContrl and can be deleted. helloworld is the project we just created.

7. When cruiscontrol starts it will do an initial build. After that first build, cruisecontrol will watch svn and if anything changes it will get the changed files then create a new build. You can also force a build by clicking the build button on the web page.

8. If you change the test case in HelloWorldTest.as to check for the string “no world” instead of “Hellow World”, cruiscontrol will show the failed unit tests on the web page.

9. Thats it your done.

2 Responses to “Continuous Integration with Flex 2 (Actionscript 3), FlexUnit, CruiseControl, Apollo and subversion on OS X”

  1. Continuous Integration with Flex « Code Adept Said:

    [...] plain old Actionscript. The runner will likely be based off an article I found from Aaron Spjut here. In a nutshell we will create a test runner in Adobe AIR that will generate XML output similar to [...]

  2. Amrish Said:

    Hi,

    can you please tell me how can we use Flash.FileSystem.File while creating the unit test for Web Application in Flex

Leave a Reply