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

(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;

    public class ZoomButtonTestCase extends TestCase
    {
        public 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 < 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;

    public class ZoomButton extends Button
    {
        public var map:Map;
        public function ZoomButton()
        {
            super();
        }
        public function doZoom():void{

        }
    }
}

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);
        }

        override public function zoomIn():void{
            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[

            public function getTestSuites() : Array
            {
                 var suiteArray : Array = new Array();
                suiteArray.push( new ZoomButtonTestSuite());
                return suiteArray;
            }
        ]]>
    </mx:Script>
</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}'>

       <fileset dir='${deploypath.dir}/tests'>
          <include name='**/*TestModule.swf'/>
       </fileset>
    </fluint>
</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.

Advertisements

About Ruprict

I am a nerd that is a Nerd Wannabe. I have more kids than should be allowed by law, a lovely wife, and a different sense of humor than most. I work in the field of GIS, where I am still trying to find myself on the map. View all posts by Ruprict

7 responses to “Continuous Integration with Flex, Hudson, and ArcGIS Server, Part 3

  • Jonathan Hartley

    Hey, this is totally great.

    I was really looking forward to you getting into writing the tests, so I could start to find out how your process compares and contrasts with what I’m used to doing in Python. (specifically IronPython, so it’s still .Net)

    Obviously there are a lot of similarities. The overall structure of the test code is the same. The Python standard library’s ‘unittest’ follows the same xUnit pattern of suites and test methods that you describe here.

    It’s interesting to me that you use a GUI test runner. Out of interest, can you set it up to do cool stuff like detect when files are changed, and re-run the appropriate tests, so that every time you hit ‘save’ in your editor you get updated unit test results? I find that dead handy when I’m in an environment where I can make it happen, but it’s not always straightforward. (eg. IronPython has a long startup time, several seconds, so that has an real impact)

    • ruprict

      I used the GUI test runner b/c that is the only thing Fluint offers. However, the very nature of Flash means we are gonna have to use the Flash Player, and there are no non-UI versions of that to my knowledge. I am unaware of any non-flash player environments for Flash, but it would be cool (and perfect for this) if they exist.

      You could write something to monitor the files and kick off a test- run, meaning, you’d have a Flash app just hanging out in the background, monitoring files. Sounds like a really good idea for an AIR app (or, maybe, a mod to the existing AIR app)

      That’s probably the biggest difference in the unit testing of Flex/ Flash vs .NET/Ruby/Python, is the need for the player. I am hoping, tho, that I am dumb and wrong and someone reads this and tells me how I am dumb and wrong.

  • Continuous Integration with Flex, Hudson, and ArcGIS Server, Part 1 « Fumbling Towards Geekstacy

    […] Posted by ruprict in Agile, Flex. Tags: Continuous Integration, Flex trackback (Part 1, Part 2, Part 3, Part […]

  • Akilan

    Hi,
    The article is really great.I’m new to running flex unit testcases using fluint AirRunner.Can you please tell me how to create flex modules for the tests and provide me the example file?

    • ruprict

      If you install the FluintAirTestRunner from this list. It will put a FluintTestAirRunner directory in your Program Files (on Windows) or whereever AIR installs stuff on your OS.

      The sample module is in the Fluint source at samples/src/test/flex/AirTestModule.mxml, which you can see on their SVN here.

      Good luck.

  • Continuous Integration with Flex, Hudson, and ArcGIS Server-Part V « Fumbling Towards Geekstacy

    […] December 11, 2009 Posted by ruprict in Continuous Integration, Flex. trackback (Part 1, Part 2, Part 3,Part 3, Part […]

  • sandeep

    Excellent Article…Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: