Monday, April 21, 2008

FlexUnit + ANT + AIR

I'm a big fan of Unit Testing and Continuous Integration. Peter Martin blogged about how to achieve this for Flex. Basically, he created an Ant task that starts a server socket connection and forks a browser that launches a flexunit based application. The application opens a client socket connection back to the server and streams the flexunit results in JUnit XML format. The server reads the xml stream and writes it on the local disk.
Notice that all of this is done so that a flex application in a browser can write to the local disk. Pretty clever, but this is where AIR makes life a bit easier. A Flex application using AIR can write to a local disk drive. Since the source code of JUnitTestRunner was available (thanks Peter), I was able to 1) learn something and 2) adjust the sendResults function to write the result to disk.

private function sendResults() : void
{
for each (var report : Object in reports)
{
var xml : XML = createXMLReport(report);
var utf : String = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml.toString();

var file : File = new File();
if( m_basedir )
{
file = file.resolvePath( m_basedir + "/target/TEST-" + xml.@name + ".xml");
}
else
{
file = File.userDirectory.resolvePath("TEST-" + xml.@name + ".xml");
}
var fs : FileStream = new FileStream();
fs.open(file, FileMode.WRITE);
fs.writeUTFBytes(utf);
fs.close();
}
}

Next, was to compile the application in Ant as part of the CI process. Here is the target that calls mxmlc:

<target name="compile">
<java jar="${flex.home}/lib/mxmlc.jar" fork="true" failonerror="true" maxmemory="256m">
<jvmarg value="-Dapplication.home=${flex.home}"/>
<jvmarg value="-Dsun.io.useCanonCaches=false"/>
<arg line="-compiler.headless-server"/>
<arg line="-library-path+='libs/flexunit.swc'"/>
<arg line="-output='target/junitair.swf'"/>
<arg line="+configname=air"/>
<arg line="src/junitair.mxml"/>
</java>
<copy file="src/junitair-app.xml" todir="target"/>
</target>

The last task in the target copies the application configuration to the build directory. I have modified the configuration file with the following entry:

<content>junitair.swf</content>

Now to run the application - I admit that I cheated here a bit :-( where, rather than creating a "real" AIR application with it full signature and having to "allow" it to run everytime as it has been changed during the compilation, I use the adl executable as follows:

<target name="run">
<exec executable="adl" failonerror="true">
<arg value="target/junitair-app.xml"/>
<arg value="--"/>
<arg value="${basedir}"/>
</exec>
</target>

I've created a project that contains a build.xml and the junitair.mxml application. Make sure to download the flex sdk. Create an environment variable FLEX_HOME that points to where you installed the sdk, and make sure that $FLEX_HOME/bin is in your path so that adl can be execute.

3 comments:

Hannu said...

Hi. I was trying this on Windows, but it didn't work because of the reference to adl. I'd rather use the full reference ${flex.home}/bin/adl. But there's still an issue on Windows because it should run the ald.exe. This can be solved with conditions checking the OS.

thunderhead said...

You are right - you have to adjust to the following:
<target name="run">
<exec executable="${flex.home}/bin/adl" failonerror="false" osfamily="mac">
<arg value="target/testair-app.xml"/>
<arg value="--"/>
<arg value="${basedir}"/>
</exec>
<exec executable="${flex.home}/bin/adl.exe" failonerror="false" osfamily="winnt">
<arg value="target/testair-app.xml"/>
<arg value="--"/>
<arg value="${basedir}"/>
</exec>
<junitreport todir="target">
<fileset dir="target">
<include name="TEST-*.xml"/>
</fileset>
<report format="frames" todir="${report.dir}"/>
</junitreport>
</target>

Tristan said...

Thanks for the post. Another alternative is to simply set up the debug log in the Flash player on launch (using mm.cfg in your home directory and setting up trace logging and error reporting as shown here: http://livedocs.adobe.com/flex/201/html/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Book_Parts&file=logging_125_04.html ). You can still use an XML report format as in JUnitTestRunner, but replace the socket writes with simple trace() calls. Then you can easily grab the XML from the log file after the tests are complete, and even include all trace output as an artifact/attachment to the build in your CI.

This removes the requirement of using the AIR libraries in your test runner, and seems like a more streamlined way to do it. Hope it helps someone else.