Run Flex Unit Tests from ANT

If you have any troubles understanding this post, make sure to revisit some of the older in this series as I assume you already have your build file and properties file set up.

Advanced Continuous Integration Topic Number 2: Automated Unit Testing.

This magic behind this is brought to you from the good guys at FlexUnit.org. They have made it possible to have the Flex (or AS3) Tester App output information to the FlexUnitTask.jar through a CIListener to be output to pretty little report files that can be read from ANT or humans.

This is not a post on how to write a unit test, best practices on unit tests, or how to set up FlexUnit with your project, I am going to assume you already understand that part.

With other languages running Unit Tests is relatively straight forward. You write your test, you run your test. What makes running Unit Tests more difficult in the Flash Player is the runtime environment. To actually run your tests you need to start up the Flash Player runtime environment and run within the Flash Player. Which means you have to compile you code, run it in Flash Player, and then output the results somewhere that they can be read by ANT to give you the results. Not an easy task, but that is what we are going to cover.

So first, you have your separate FlexUnit Project.

Get Adobe Flash player


This project runs the FlexUnit Test Runner to run the Unit Tests that you’ve created for your code.

This particular application have intentional faults so that you can see both success and fault responses. Typically you would work to have no failed tests.

I make this a completely separate project from my main application specifically because I want to keep my main application as clean from clutter and additional code as possible. Can you combine the two? Yes. Do I? No. You may think that having the second application wouldn’t add much clutter, but I personally feel when you are dealing with Enterprise applications – and therefore many developers – the more simplistic that you can make your code base the better. With many developers – each of different skill levels – you don’t want to overload some developers that might have a harder time understanding the full set of code and tests and everything else. On top of that, you can have your bug testers work with an application without getting the ability to hurt your core application code. Again, the separation of code and duties will help you scale.

You can hit View Source and see the unit tests that I have created and also get an ANT build file to run just the FlexUnit Project in ANT.

Our Next Steps
If you have started this series from the beginning our ANT file currently builds the library, builds the application into a deploy folder, copies the non-embedded assets into the deploy folder, builds our html-wrappers into the deploy folder, and also builds our ASDocs from our library and also our application into an asdoc folder.

Now we are going to add the following steps into the build.

  • Clean and create a test folder to hold the built test application
  • Clean and create a report directory to hold your flexunit reports
  • Compile the test runner application
  • Execute the test runner application
  • Generate the readable reports from the test
  • Clean up (destroy) the test folder and the compiled test runner application

The first two parts are simple, we’ve done it multiple times already.
Added into your build file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    <target name="cleanFlexUnitReport" description="Cleans the Report file">
        <echo>Deleting FlexUnit Report Directory...</echo>
        <delete dir="${FlexUnitReport.dir}" failOnError="false"
            includeEmptyDirs="true" />
        <echo>Deleted FlexUnit Report Directory</echo>
        <echo>Creating FlexUnit Report Directory...</echo>
        <mkdir dir="${FlexUnitReport.dir}" />
        <echo>Created FlexUnit Report Directory</echo>
    </target>
   
    <target name="cleanTest" description="Cleans the Test file">
        <echo>Deleting Test Directory...</echo>
        <delete dir="${Test.dir}" failOnError="false"
            includeEmptyDirs="true" />
        <echo>Deleted Test Directory</echo>
        <echo>Creating Test Directory...</echo>
        <mkdir dir="${Test.dir}" />
        <echo>Created Test Directory</echo>
    </target>

Added into your properties file.

1
2
3
4
#report output file
FlexUnitReport.dir=${basedir}/flexUnitReport
#test dir
Test.dir=${basedir}/test

Compile The Test Runner Application
Now we have the the folders necessary to hold all of our output, now we need to compile the test runner application. This part is going to be very similar to building the application because we will again be using functionality provided to us by flexTasks.jar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    <target name="compileTestRunner">
        <echo>Compiling Test Runner SWF To Test</echo>
        <mxmlc file="${TestRunnerSrc.dir}/${TestRunner.name}.mxml"
            output="${Test.dir}/${TestRunner.name}.swf">
            <load-config filename="${FLEX_HOME}/frameworks/flex-config.xml"/>
            <compiler.source-path path-element="${Src.dir}" />
            <compiler.library-path dir="${basedir}/" append="true">
                <include name="${DeploySWC.dir}"/>
            </compiler.library-path>
            <compiler.library-path dir="${TestRunnerLib.dir}" append="true">
                <include name="*.swc"/>
            </compiler.library-path>
            <compiler.verbose-stacktraces>true</compiler.verbose-stacktraces>
            <compiler.headless-server>true</compiler.headless-server>
        </mxmlc>
        <echo>Compiled Test Runner SWF To Test</echo>
    </target>

Here we set up the task and compile the project with any required libraries from the unit test project along with any libraries from our main application – so all dependencies are included, and the source code from the unit test project – our actual tests. The swf output from this compile task is placed into our test folder which later will be destroyed after it is no longer needed.

Here are the build properties added because of this task.

1
2
3
4
5
6
#Test Runner Dir
TestRunner.dir=C:/workspace/AntBuildUnitTest
TestRunnerSrc.dir=${TestRunner.dir}/src
TestRunnerLib.dir=${TestRunner.dir}/libs
#Test Runner Dir
TestRunner.name=ANTBuildUnitTest

Executing The Test Runner
For this next part we need to add some additional capabilities to our ANT build file via the flexUnitTasks jar.

We will accomplish this addition the same way we added in the flexTasks.jar in the beginning. At the top of our build file, under the flexTasks.jar taskdef tag, add:

1
<taskdef resource="flexUnitTasks.tasks" classpath="${FlexUnitTasks.file}"/>

And in your properties just include the path to the file.

1
2
#flextasks jar location
FlexUnitTasks.file=flexTasks/flexUnitTasks-4.0.0.jar

Now with the flexUnitTasks jar added to our build file, we can use the flexunit task. This task allows us to run the compiled test runner application and output the results to a folder of our choice.

Once you have the generated output, you can use the the compatible (and built in) junitreport task to create nice to read xml reports.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    <target name="executeTestRunner" description="executes the test runner app">
        <echo>Running Test Runner SWF</echo>
        <flexunit swf="${Test.dir}/${TestRunner.name}.swf"
            toDir="${FlexUnitReport.dir}"
            haltonfailure="false"
            verbose="true"
            localTrusted="true"/>
        <echo>Ran Test Runner SWF</echo>
       
        <echo>Generate Readable Tests</echo>
        <junitreport todir="${FlexUnitReport.dir}">
            <fileset dir="${FlexUnitReport.dir}">
                <include name="TEST-*.xml"/>
            </fileset>
            <report format="frames" todir="${FlexUnitReport.dir}/html"/>
        </junitreport>
        <echo>Generated Readable Tests</echo>
    </target>

One thing I want to point out is the haltonfailure option. I set mine to false so that the build script keeps running, but you can also set this to true so that the build script fails immediately if an error in your unit tests come up. Dependent on your situation, this would be the only flag you may want to change.

Current File
There is only one more major task left to really make build file complete, automated testing, the brother of unit testing – a little bit different, equally important.

Currently your build script should look similar to this:

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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
<?xml version="1.0" encoding="UTF-8"?>
<project name="Build File" basedir="." default="compileProject">
   
    <!-- Set Up ================================================= -->
   
    <!-- file description -->
    <description>Build Script</description>
   
    <!--location of property file -->
    <property file="./build.properties"
        description="properities for builds" />
   
    <!-- timestamp -->
    <tstamp>
        <format property="timestamp" pattern="yyyyMMdd" />
    </tstamp>
   
    <!-- additional tasks -->
    <taskdef resource="flexTasks.tasks" classpath="${FlexTasks.file}"/>
    <taskdef resource="flexUnitTasks.tasks" classpath="${FlexUnitTasks.file}"/>
   
    <!-- ======================================================== -->
   
   
    <!-- Compile Project ======================================== -->
    <target name="compileProject"
        depends="cleanDeploy,buildWrapper,buildCustomWrapper,copyNonEmbeddedFiles,compileLibraries,runFlexUnitTests,compileApplication,asDocs,cleanTempDirectories,zipApplication"
        description="compiles application"/>
   
    <!-- Build Wrappers ========================================= -->
    <target name="buildWrapper">
        <echo>Building Wrapper...</echo>
        <html-wrapper title="${Application.name}"
            height="100%" width="100%"
            bgcolor="#FFFFFF" file="${Application.name}.html"
            application="${Application.name}"
            swf="${Application.name}${timestamp}"
            version-major="${Major.version}"
            version-minor="${Minor.version}"
            version-revision="${Revision.version}"
            history="true"
            template="express-installation"
            output="${Deploy.dir}" />
        <echo>Built Wrapper</echo>
    </target>
   
    <target name="buildCustomWrapper">
        <echo>Building Custom Wrapper...</echo>
        <copy file="${Template.file}" tofile="${Deploy.dir}/${Output.file}" overwrite="true">
            <filterchain>
                <replacetokens>
                    <token key="version_major" value="${Major.version}"/>
                    <token key="version_minor" value="${Minor.version}"/>
                    <token key="version_revision" value="${Revision.version}"/>
                    <token key="application" value="${Application.name}"/>
                    <token key="width" value="100%"/>
                    <token key="height" value="100%"/>
                    <token key="bgcolor" value="#FFFFFF"/>
                    <token key="swf" value="${Application.name}${timestamp}"/>
                </replacetokens>
            </filterchain>
        </copy>
        <echo>Built Custom Wrapper</echo>
    </target>
    <!-- ======================================================== -->
   
    <!-- Compile Application ==================================== -->
    <target name="compileApplication">
        <echo>Compiling SWF To Deploy</echo>
        <mxmlc file="${Src.dir}/${Application.name}.mxml"
            incremental="false"
            actionscript-file-encoding="UTF-8"
            output="${Deploy.dir}/${Application.name}${timestamp}.swf"
            debug="${Debug.Boolean}"
            keep-generated-actionscript="false">
            <load-config filename="${FLEX_HOME}/frameworks/flex-config.xml"/>
            <default-background-color>0xFFFFFF</default-background-color>
            <metadata>
                <creator>Jonathan Campos</creator>
                <publisher>UnitedMindset</publisher>
                <language>EN</language>
            </metadata>
            <compiler.source-path path-element="${Src.dir}" />
            <compiler.library-path dir="${basedir}/${DeploySWC.dir}" append="true">
                <include name="*.swc"/>
            </compiler.library-path>
        </mxmlc>
        <echo>Compiled SWF To Deploy</echo>
    </target>
    <!-- ======================================================== -->
   
    <!-- NONEMBEDDED FILES ====================================== -->
    <target name="copyNonEmbeddedFiles">
        <echo>Deleting Deploy Assets Directory...</echo>
        <delete dir="${DeployAssets.dir}" failOnError="false" includeEmptyDirs="true" />
        <echo>Deleted Deploy Assets Directory</echo>
        <echo>Creating Assets Folder</echo>
        <mkdir dir="${DeployAssets.dir}"/>
        <echo>Created Assets Folder</echo>
        <echo>Copy Nonembedded Resources To Deploy</echo>
        <copy todir="${DeployAssets.dir}" includeemptydirs="false" overwrite="true">
            <fileset dir="${Assets.dir}"/>
        </copy>
        <echo>Copied Nonembedded Resources To Deploy</echo>
    </target>
    <!-- ======================================================== -->
   
    <!-- COMPILE LIBRARIES ====================================== -->
    <target name="compileLibraries" depends="cleanDeploySWCs,copyPrecompiledSWCs,compileLibrary" />

    <target name="cleanDeploySWCs">
        <echo>Deleting Deploy SWC Directory...</echo>
        <delete dir="${DeploySWC.dir}" failOnError="false" includeEmptyDirs="true"/>
        <echo>Deleted Deploy SWC Directory</echo>
        <echo>Creating Deploy SWC Directory...</echo>
        <mkdir dir="${DeploySWC.dir}"/>
        <echo>Created Deploy SWC Directory</echo>
    </target>

    <target name="copyPrecompiledSWCs">
        <echo>Copying to Deploy SWC Directory...</echo>
        <copy todir="${DeploySWC.dir}" includeemptydirs="false" overwrite="true">
            <fileset dir="${ApplicationLibs.dir}"/>
        </copy>
        <echo>Copied to Deploy SWC Directory...</echo>
    </target>
   
    <target name="compileLibrary" description="compiles the Library">
        <echo>Compiling Library SWC To Deploy SWC Folder</echo>
        <compc debug="${Debug.Boolean}" output="${DeploySWC.dir}/${Library.name}.swc">
            <source-path path-element="${LibrarySrc.dir}"/>
            <include-sources dir="${LibrarySrc.dir}" includes="*"/>
            <compiler.library-path dir="${LibraryLibs.dir}" append="true">
                <include name="*.swc"/>
            </compiler.library-path>
            <metadata>
                <creator>Jonathan Campos</creator>
                <publisher>UnitedMindset</publisher>
                <language>EN</language>
            </metadata>
        </compc>
        <echo>Compiled Library SWC To Deploy SWC Folder</echo>
    </target>
   
    <!-- Clean Up =============================================== -->
    <target name="cleanDeploy" description="Cleans the deploy file">
        <echo>Deleting Deploy Directory...</echo>
        <delete dir="${Deploy.dir}" failOnError="false"
            includeEmptyDirs="true" />
        <echo>Deleted Deploy Directory</echo>
        <echo>Creating Deploy Directory...</echo>
        <mkdir dir="${Deploy.dir}" />
        <echo>Created Deploy Directory</echo>
    </target>
   
    <target name="cleanTempDirectories">
        <echo>Deleting Deploy SWC Directory...</echo>
        <delete dir="${DeploySWC.dir}" failOnError="false" includeEmptyDirs="true"/>
        <echo>Deleted Deploy SWC Directory</echo>
       
        <echo>Deleting Test Directory...</echo>
        <delete dir="${Test.dir}" failOnError="false"
            includeEmptyDirs="true" />
        <echo>Deleted Test Directory</echo>
    </target>

    <!-- ======================================================== -->
   
    <!-- ASDOC ================================================== -->
    <target name="asDocs" depends="cleanASDoc,compileASDoc" description="build of asdocs" />

    <!-- Delete the existing output folder and files and then regenerate the output folder -->
    <target name="cleanASDoc">
        <echo>Deleting ASDoc Directory...</echo>
        <delete dir="${Asdoc.dir}" failOnError="false" includeEmptyDirs="true" />
        <echo>Deleted ASDoc Directory</echo>
        <echo>Creating ASDoc Directory...</echo>
        <mkdir dir="${Asdoc.dir}" />
        <echo>Created ASDoc Directory</echo>
    </target>

    <!-- Run the ASDoc executable and generate the ASDocs to the new output folder -->
    <target name="compileASDoc">
        <echo>ASDoc Compiling...</echo>
        <exec executable="${AsDocs.executable}" failonerror="true">
            <arg line="-doc-sources '${AppClasses.dir}'" />
            <arg line="-doc-sources '${LibrarySrc.dir}'" />
            <arg line="-external-library-path '${LibrarySrc.dir}'" />
            <arg line="-external-library-path '${Library.dir}/${LibraryLibs.dir}'" />
            <arg line="-main-title '${Main.title}'" />
            <arg line="-window-title '${Window.title}'" />
            <arg line="-output '${Asdoc.dir}'" />
            <arg line="-footer '${Footer.text}'" />
        </exec>
        <echo>ASDoc Compile Complete</echo>
    </target>
    <!-- ======================================================== -->
   
    <!-- FlexUnit =============================================== -->
    <target name="runFlexUnitTests"
        description="run flexunit tests"
        depends="cleanFlexUnitReport,cleanTest,compileTestRunner,executeTestRunner"/>
   
    <target name="cleanFlexUnitReport" description="Cleans the Report file">
        <echo>Deleting FlexUnit Report Directory...</echo>
        <delete dir="${FlexUnitReport.dir}" failOnError="false"
            includeEmptyDirs="true" />
        <echo>Deleted FlexUnit Report Directory</echo>
        <echo>Creating FlexUnit Report Directory...</echo>
        <mkdir dir="${FlexUnitReport.dir}" />
        <echo>Created FlexUnit Report Directory</echo>
    </target>
   
    <target name="cleanTest" description="Cleans the Test file">
        <echo>Deleting Test Directory...</echo>
        <delete dir="${Test.dir}" failOnError="false"
            includeEmptyDirs="true" />
        <echo>Deleted Test Directory</echo>
        <echo>Creating Test Directory...</echo>
        <mkdir dir="${Test.dir}" />
        <echo>Created Test Directory</echo>
    </target>
   
    <target name="compileTestRunner">
        <echo>Compiling Test Runner SWF To Test</echo>
        <mxmlc file="${TestRunnerSrc.dir}/${TestRunner.name}.mxml"
            output="${Test.dir}/${TestRunner.name}.swf">
            <load-config filename="${FLEX_HOME}/frameworks/flex-config.xml"/>
            <compiler.source-path path-element="${Src.dir}" />
            <compiler.library-path dir="${basedir}/" append="true">
                <include name="${DeploySWC.dir}"/>
            </compiler.library-path>
            <compiler.library-path dir="${TestRunnerLib.dir}" append="true">
                <include name="*.swc"/>
            </compiler.library-path>
            <compiler.verbose-stacktraces>true</compiler.verbose-stacktraces>
            <compiler.headless-server>true</compiler.headless-server>
        </mxmlc>
        <echo>Compiled Test Runner SWF To Test</echo>
    </target>
   
    <target name="executeTestRunner" description="executes the test runner app">
        <echo>Running Test Runner SWF</echo>
        <flexunit swf="${Test.dir}/${TestRunner.name}.swf"
            toDir="${FlexUnitReport.dir}"
            haltonfailure="false"
            verbose="true"
            localTrusted="true"/>
        <echo>Ran Test Runner SWF</echo>
       
        <echo>Generate Readable Tests</echo>
        <junitreport todir="${FlexUnitReport.dir}">
            <fileset dir="${FlexUnitReport.dir}">
                <include name="TEST-*.xml"/>
            </fileset>
            <report format="frames" todir="${FlexUnitReport.dir}/html"/>
        </junitreport>
        <echo>Generated Readable Tests</echo>
    </target>
   
    <!-- ======================================================== -->
   
</project>

And your properties:

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
#####################################################################
#
#  Compiler Properties
#
#####################################################################

# Flex SDK File Location
FLEX_HOME=C:/Program Files/Adobe/Flex Builder 3 Plug-in/sdks/3.3
#flextasks jar location
FlexTasks.file=flexTasks/flexTasks.jar
#deploy directory
Deploy.dir=deploy
#source directory
Src.dir=src
#debug flag
Debug.Boolean=false
#application name
Application.name=ANTBuildProject
#version major
Major.version=9
#version minor
Minor.version=0
#version revision
Revision.version=124
#Template File
Template.file=index.template.html
#Output HTML File
Output.file=index.html
#Assets
Assets.dir=${Src.dir}/assets
#Assets
DeployAssets.dir=${Deploy.dir}/assets
#Deploy SWC Directory
DeploySWC.dir=deploySWC
#Library Directory
Library.dir=C:/workspace/ANTBuildLibrary
#Library Libs
LibraryLibs.dir=${Library.dir}/libs
#Library Src
LibrarySrc.dir=${Library.dir}/src
#library name
Library.name=ANTBuildLibrary
#Application Libs
ApplicationLibs.dir=${basedir}/libs

#####################################################################
#
#  ASDoc Properties
#
#####################################################################
#asdoc output
Asdoc.dir=asdoc
#the location of asdocs on your computer
AsDocs.executable=${FLEX_HOME}/bin/asdoc.exe
#Asdoc Footer Text
Footer.text=Some legal jargon
#window title
Window.title=Window Title
#main title
Main.title=Main Title
#the location of your application classes on your computer
AppClasses.dir=${basedir}/src

#####################################################################
#
#  FlexUnit Properties
#
#####################################################################
#report output file
FlexUnitReport.dir=${basedir}/flexUnitReport
#test dir
Test.dir=${basedir}/test
#flextasks jar location
FlexUnitTasks.file=flexTasks/flexUnitTasks-4.0.0.jar
#Test Runner Dir
TestRunner.dir=C:/workspace/AntBuildUnitTest
TestRunnerSrc.dir=${TestRunner.dir}/src
TestRunnerLib.dir=${TestRunner.dir}/libs
#Test Runner Dir
TestRunner.name=ANTBuildUnitTest

You’ll notice the one place I didn’t spend time showing the changes is the main targets, including the compileProject target. As I add functionality I automatically write in this functionality to the main compileProject target and make up unique subtarget. How you set up your file and it’s parts is up to you.

  • Share/Bookmark

Comments (5)

[...] FlexUnit tests I am going to assume a few things: 1) You already have a working understanding of FlexMonkey. 2) [...]

odoeFebruary 5th, 2010 at 4:36 pm

I’ve been reading your ANT posts so far and they have been a great help in learning ANT and to make my iteration deployment easier. Thanks.

I ran into one problem with trying to generate my FlexUnit report using your example.
I downloaded your example and just changed some paths in the properties file for my machine and everything seems to work fine, except the TESTS-TestSuites.xml is empty. The XML files for the unit tests have information, just not the suite, so the report has 0′s for all results.
I tried it at work and at home, and even on a simple project from scratch. Not sure if I’m missing something.

Thanks, your articles have been a big help so far.

Jonathan CamposFebruary 5th, 2010 at 4:50 pm

Well that’s not good. That means I put up the files from an older build that I had that same issue. I would double check your “Generate Readable Tests” and look at the section. Make sure it is When originally putting this together I accidentally put an ‘s’ on the end of TEST and that causes issues. Sorry for that. Try removing that and let me know how it goes.

odoeFebruary 5th, 2010 at 6:39 pm

That was it! Works perfectly now. Thanks. Nice work on the series.

IlyaMay 31st, 2010 at 4:15 pm

Thank you so much! My build.xml works at last.

Leave a comment

Your comment