Continuous Integration with Flex, Hudson, and ArcGIS Server, Part 3

Mon, Aug 24, 2009 9-minute read

(Part 1Part 2Part 4)

One of the tenets of continuous integration is automated testing.  The most basic form of testing, in this context, is unit testing, where each class or method is tested without running the other bits of the application.  Unit testing is focused and, hopefully, simple.  In this post, I am going to add unit testing to our application and get ant to run the tests.  There are several unit testing frameworks out there, and we’re going to use Fluint. Fluint and FlexUnit have kinda merged into FlexUnit4, which is in alpha.  FlexUnit4 is, howyousay, super-fantastic, but it doesn’t currently offer any ant tasks.  When it does, I’ll be moving to it post haste.

Go get Fluint from here.  Put the fluint-1.1.1.swc file in the libs folder of your project.  We are now ready to start with some simple Test Driven Development.  Let’s add a button that zooms the map one level in, shall we?  As all examples, it’s contrived, but the point is to focus on the process.   The process here will follow simple TDD: write a test, watch it fail, write the code to make it pass.  In order to be able to watch the test fail, we’re need a bit of a foundation.  For the sake of this article, I have made a “tests” folder in the src directory where I will store the test bits.  Also, we will need a test runner for when we want to manually run our test suite.  That can be downloaded from here and copied to the src directory as well.  Once the runner (FlexTestRunner.mxml) is in the src dir, be sure to go into the project properties and add it to the list of Flex Applications for the project.  if you’ve done all that properly, then you will be able to run the FlexTestRunner application, which runs the Fluint test suites, by default.

image

We don’t want to run the framework tests, so remove all mentions of FrameworkSuite from the code in FlexTestRunner.mxml.  In Fluint, a test runner runs test suites and test suites are composed of test cases.  We will make our own test suite, called ZoomButtonTestSuite, and our own test case called (you guessed it) ZoomButtonTestCase.  These live in the aforementioned “tests” dir.

The test suite is simple:

package tests
{
    import net.digitalprimates.fluint.tests.TestSuite;
    public class ZoomButtonTestSuite extends TestSuite
    {
        public function ZoomButtonTestSuite()
        {
            addTestCase(new ZoomButtonTestCase());
        }

}

}

All we do is pull in our test case, which looks like:
package tests
{
    import net.digitalprimates.fluint.tests.TestCase;

<span class="kwrd">public</span> <span class="kwrd">class</span> ZoomButtonTestCase extends TestCase
{
    <span class="kwrd">public</span> function ZoomButtonTestCase()
    {
        super();
    }
}

}

Before we go on, go back to FlexTestRunner.mxml and add ZoomButtonTestSuite to the suiteArray in startTestProcess, so it looks like:
protected function startTestProcess( event:Event ) : void
{
    var suiteArray:Array = new Array();
    suiteArray.push( new ZoomButtonTestSuite() );

testRunner.startTests( suiteArray );

}

We should be ready to right our first test.  For the ZoomButton, here are the specs:
  • When the user clicks the button, zoom the map in one level.
Pretty complicated, eh?  Here’s the test:
public function testZoomIn():void{
    var button:ZoomButton = new ZoomButton();
    var beforeLevel:uint= button.map.level;
    button.doZoom();

assertTrue(button.map.level &lt; beforeLevel);

}

I have started with the button API design, which is the real aim of our test.  Just from this test, you can see that:
  • The button is it’s own class.
  • The button has a property called map.
  • The button has a method called doZoom() (it has to be public for this example…not sure i like that)
  • The doZoom() method changes the map level.
This won’t even compile, so let’s get that happening.
package widgits
{
    import com.esri.ags.Map;
    import mx.controls.Button;

<span class="kwrd">public</span> <span class="kwrd">class</span> ZoomButton extends Button
{
    <span class="kwrd">public</span> var map:Map;
    <span class="kwrd">public</span> function ZoomButton()
    {
        super();
    }
    <span class="kwrd">public</span> function doZoom():<span class="kwrd">void</span>{

    }
}

}

OK.  The ZoomButton extends the Button class, has a map property (setter/getter not used for brevity) and a doZoom() method.  Launch the test runner, and you’ll see:

image

About what we’d expect.  The button.map property is null.  However, I don’t want to create a full blown ESRI map for my tests.  It will be clunky and slow and difficult.  We need to fake the map.  This is where Mocks come in.

Mocking What We Don’t (need to) Understand

Just like unit testing frameworks, there are quite a few mocking frameworks in Flex land.  For this article, I am going to use MockAS3. (You have to build from source.  Or you can leave a comment with an e-mail and I’ll send you my copy).  Mock AS3 is nice, as it lets us mock a class and not just an interface.  ESRI does not implement an interface with its controls (like Map) for reasons that I don’t know, but I wish they would.  Also, this unit test is an interaction-based test, meaning we are going to set expectation of how we expect the ZoomButton and the map to interact, then verify those expectations as part of the test.  This is what mocking is, testing interactions and expectations presuming all dependent objects do what they are supposed to do.

So, what do we expect the ZoomButton to do in the doZoom() method?  We expect it to call map.zoomIn(), right?  Our first, naive test was testing properties on the map.  While this is testing a valid post-condition, it bleeds too far into testing the ESRI Map control and not the ZoomButton.  After all, the ZoomButton does not set the map level, it just calls a method on the map. If it does that, we are happy with our ZoomButton.  Any issues outside of it are not related to the ZoomButton and its purpose.  With this in mind, our new test looks like:

public function testZoomIn():void{
        var button:ZoomButton = new ZoomButton();
        var mapMock:MapMock = new MapMock();
        mapMock.mock.method(‘zoomIn’).once;

    button.doZoom();

    mapMock.mock.verify();

}

The new test now creates a mock object for the ZoomButton.map property and tells it to expect the “zoomIn” method to be called one time.  We then test the doZoom method, followed by asking the mock to verify that our expectation was met.  Here is the mock:
package tests.mocks
{
    import com.anywebcam.mock.Mock;
    import com.esri.ags.Map;
    public class MapMock extends Map
    {
        public var mock:Mock;
        override public function MapMock()
        {
            mock = new Mock(this);
        }

    <span class="kwrd">override</span> <span class="kwrd">public</span> function zoomIn():<span class="kwrd">void</span>{
        mock.zoomIn();
    }

}

}

This follows the pretty-good documentation on the Mock AS3 Google Code site for mocking classes.  We only need to mock the methods on which we are setting expectations.  Build and launch the test runner again.

image

Test still fails, but we get a new error:  Verifying Mock Failed. We haven’t coded our doZoom() method yet.

public function doZoom():void{
    if (map)
        map.zoomIn();
    else
        throw new Error(“Map is not set”);
}
Now, running the tests gives us sweet green….

image

Test Ant, Test Ant…

Now, we need to get these tests running with ant.  Luckily, Fluint ships with ant tasks (which are not available for FlexUnit4 at the time of this writing, but they assure me they are coming)  However, the ant tasks will not use the FlexTestRunner.mxml.  They use a different test runner with is Adobe AIR based, called (you guessed it) AirTestRunner, which you can download from here.  Double clicking on the .air files will launch the AIR installer.  Just follow all the prompts and it will install the FlexAirTestRunner to C:\Program Files\FluintAIRTestRunner.  In that directory you’ll see a .EXE file as well as a .SWF file, which is the actual test runner.  Another caveat to running the Fluint tests with ant is that the AIR test runner requires the use of Flex Modules.  What this means is that you have to create one or more Flex Modules for your tests, depending on how you want to structure the tests.  Basically, each module acts like a container for TestSuites that you want to run, then add the module to the ant task.  Enough talk.   A sample test module can be downloaded from the Fluint Google Code site, here or can be easily written.  A Fluint test module implements the ITestModule interface, which has a single method: getTestSuites().  Here is ours:
<?xml version=“1.0” encoding=“utf-8”?>
<mx:Module xmlns:mx=http://www.adobe.com/2006/mxml" implements=“net.digitalprimates.fluint.modules.ITestSuiteModule”>
    <mx:Script>
        <![CDATA[

        <span class="kwrd">public</span> function getTestSuites() : Array
        {
             var suiteArray : Array = <span class="kwrd">new</span> Array();
            suiteArray.push( <span class="kwrd">new</span> ZoomButtonTestSuite());
            <span class="kwrd">return</span> suiteArray;
        }
    ]]&gt;
&lt;/mx:Script&gt;

</mx:Module>

Looks a lot like the FlexTestRunner.mxml, huh?  The only thing we have left to do now is add a test target to our ant script.  As some of you might have guessed, we need to add the Fluint ant tasks to our build.xml file.  The easiest (read: not necessarily RIGHT) way to do this is copy them into the c:\ant\lib directory.  You can get the JAR file with the ant tasks here.   Once they are in the ant lib directory, add the following line to your build.xml (right under the flexTasks is a good spot)
<taskdef name=‘fluint’ classname=‘net.digitalprimates.ant.tasks.fluint.Fluint’  />
There, now we can use our Fluint tasks in the ant file.  Since the tests are using Flex modules, we’ll want to have a couple of targets in the build script: one that builds the module, and one that executes the test.  Heres’ the target that builds the module:
<target name=“build-test-module”>
     <echo>Building test module</echo>
    <mxmlc file=’${srcpath.dir}/tests/ZoomButtonTestModule.mxml’  target-player=“10.0.0”
        output=’${deploypath.dir}/tests/ZoomButtonTestModule.swf’ >
         <load-config filename=’${FLEX_HOME}/frameworks/flex-config.xml’ />
         <source-path path-element=’${FLEX_HOME}/frameworks’/>
         <!– source paths –>
         <compiler.source-path path-element=’${srcpath.dir}’/>
        <!– add external libraries –>
        <include-libraries file=’${libs.dir}’ />
    </mxmlc>
</target>
If you go to the command line and type ‘ant build-test—module’, it will build the module and copy it to our bin-debug directory. Now, we just need to tell the Fluint task to go find that module and run the test.  Here’s the target:
<target name=‘test’>
    <fluint
       debug=‘true’
       headless=‘true’
       failOnError=‘true’
       workingDir=’${fluint.dir}’
       testRunner=’${fluint.dir}/FluintAIRTestRunner.exe’
       outputDir=’${report.dir}’>

   &lt;fileset dir=<span class="str">'${deploypath.dir}/tests'</span>&gt;
      &lt;include name=<span class="str">'**/*TestModule.swf'</span>/&gt;
   &lt;/fileset&gt;
&lt;/fluint&gt;

</target>

You may have noticed the two new build variables: fluint.dir and report.dir.  The Fluint task needs to know where the FluintAirTestRunner.exe file resides, which is the former.  The latter tells fluint where to write the report of how the tests faired.   I added this to the bottom of the build.properties file:
#Fluint vars
fluint.dir = C:/Program Files (x86)/FluintAIRTestRunner
report.dir = ${basedir}/reports
Now, running ‘ant test’ at the command line will run our test and generate the test report.  It’s an XML file that looks like:
<testsuites status=“true” failureCount=“0” errorCount=“0” testCount=“1”>
  <testsuite name=“tests.ZoomButtonTestCase” errors=“0” failures=“0” tests=“1” time=“0.237”>
    <properties/>
    <testcase name=“testZoomIn” time=“0.237” className=“tests.ZoomButtonTestCase”/>
  </testsuite>
</testsuites>
So, our ant file is now running our tests.  We are well on our way to continuous integration with Flex.  Coming up, we’ll need to get some of the hardcoding out of the build file, look at our project directory structure, format our test reports to be more readable, and bring Hudson into the mix.  We have a lot to do.

I hope you find this useful.