If you think the IT industry is bad at estimating...

June 25, 2006

You should try the moving industry.

I have just hung up my boots at Fidelity to move deep undercover doing some work in New York (via Washington DC, but thats a whole other story). I'm initially going to be staying in a hotel, so I decided to put my furniture in storage. I'm also moving in kind of a hurry, so I needed to have the movers pack my stuff for me. So I asked them for a quote based on a 1 bedroom apartment, a kitchen's worth of saucepans & crockery, a PC, some furniture and a few bits and bobs.

The quote was for 3 men for 3 hours and about 3-400 dollars of packing costs. This seemed pretty reasonable (I really dont have too much stuff), so I decided to go with these guys. Anyways, I have just been charged for 3 men for 9 hours, and also been charged for 900 dollars of packing costs. I'm still so angry about this I'm not sure if its a scam, or if I didnt give them enough information, but to get an estimate out by a factor of 6 is just plain ridiculous...

FlexUnit UI factory

June 16, 2006

When I started looking into how we could use flexunit in our CI build, I was struck by how the UI is closely linked to the actual running of the test suite. I really wanted to be able to use the supplied flexunit GUI out of the box - after all, it does everything the developer needs, and yet in order to have the results understood by our build process, we would need the results printed out differently.

To acheive this, my Flexunit test harness uses a factory method pattern to decide how torunthe test suite. Here's how...

I first started by taking the skeleton flexunit testsuite implementation outlined by Darron Schall here. Next, I removed the testRunner MXML node, and changed my creationComplete function to look like this:

private function onCreationComplete():void{
  var testRunner:IFlexUnitUI = FlexUnitUIFactory.createUI();
  var visualTR:UIComponent = testRunner as UIComponent;
  addChild(visualTR) 
  visualTR.percentWidth = 100;
  visualTR.percentHeight = 100;				
  testRunner.setTest(createSuite());
  testRunner.startTest();
}

OK, so far this doesnt really tell you guys much, so lets delve a little deeper into whats going on... my test runner satisfies an interface that simply allows me to set a test and start a test. This decouples the way we run our tests (and display our results) from the main testHarness. The flexUnitUIFactory is a very simple interface:

package com.fmr.flexunit
{
	import flexunit.framework.TestSuite;
	
	public interface IFlexUnitUI
	{
		function setTest(t:TestSuite):void;
		function startTest():void;
	}
}

Now, lets look at the factory itself:

package com.fmr.flexunit
{
	import flash.system.Capabilities;
	
	//creates an IFlexUnit UI for use by the test harness.
	// the idea is that one can have either visual feedback, or a lightweight text feedback, depending on requirements
	public class FlexUnitUIFactory
	{
		public static function createUI():IFlexUnitUI{
			var environment:String = Capabilities.playerType;
			var outObj:IFlexUnitUI;
			switch(environment){
				case "StandAlone":
					outObj = new TextOutputUI();
					break;
				case "PlugIn":
				default:
					outObj = new FlexUnitUI();
					break;
			}
			return outObj;
		}
	}
}

The factory is actually very simple - it chooses a TextOutputUI if its running in a standalone player (and thus being run as an ANT build), or uses a pretty GUI if in the browser (thus being run by the developer). Each of these UI's must satisfy our interface, and so I needed to subclass the flexunit GUI to get it to satisfy the interface:

>package com.fmr.flexunit
{
	import flexunit.flexui.TestRunnerBase;
	import flash.events.Event;
	import flexunit.framework.TestSuite;
	
	public class FlexUnitUI extends TestRunnerBase implements IFlexUnitUI
	{
		public static var testsCompleteEvent:String = "testsComplete";
		override public function onAllTestsEnd() : void {
			super.onAllTestsEnd();
			dispatchEvent(new Event(testsCompleteEvent));
		}
		
		public function setTest(t:TestSuite):void{
			test = t;
		}
	}
}

The TextOutputUI uses our CruiseControl friendly outputting classes, and is simply a VBox (as we need our elememnt to be a IUObject that can be atached to our main app):

<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" implements="com.fmr.flexunit.IFlexUnitUI">
	<mx:Script>
		<![CDATA[
			import flexunit.framework.TestSuite;
			import flexunit.textui.TestRunner;
		
			public var test:TestSuite;
			
			public function setTest(t:TestSuite):void{
				test = t
			}
			public function startTest():void{
				CruiseControlTestRunner.run( test, onTestsComplete );
			}
			private function onTestsComplete():void{
				fscommand("quit");
			}
		]]>
	</mx:Script>
</mx:VBox>

Notice also that in our textOutputUI, when the tests are complete, the app is told to quit - this is a requirement of our ANT build process as once the unit tests are run, the main app should be built.

And that's basically it. Using this method, our test harness can run differently depending on its environment. I havent had a chance to test this in the field yet, but I'll let you know how it all works out...

FlexUnit for Cruise Control - XML output

June 16, 2006

If you read my earlier posts on Flex and continuous integration, you will remember that we had to do some work to get ASUnit to spit out its results in a manner that would be understood by Cruise Control. We built a log parser to parse results from the Flash players trace file into an XML file detailing the unit test results, and also a simple status file saying if the test suite succeeded or failed. Here's how I got FlexUnit to print out results that will be understood by our parser...

I based my work here on how the flexunit.textui.TestRunner and flexunit.textui.ResultPrinter class work. I created two classes: CruiseControlTestRunner and CruiseControlResultPrinter.

CruiseControlTestRunner

This class is incredibly similar to TestRunner. In fact the only difference is that our private variable printer is set up to use our other new class - CruiseControlResultPrinter. If this variable had been declared protected, then I would have subclassed TestRunner and simply changed the setup of the printer to use our new class... You can download the file here

CruiseControlTestPrinter

The printer is a little more involved, but still not too tricky. what I do here is create an XML Object and append results as they come through. TestListeners (the interface that the printer implements) have functions that get triggered when a test starts and ends, and also if a test fails, or if there is an error. These can be used to construct our XML as the suite runs. Lets look at the class bit by bit: (or you can just download the class and get started

Constructor

Dead simple, we just initialize our XML with a top-level node:

public function CruiseControlResultPrinter()
    {
        __resultXML = new XML("");
    }

startTest

When a test starts, we want to append a testcase node to our XML. Each testcase node actually needs to sit in a testsuite node, so I created a helper function to automatically create a testsiute node for the testcase if one doesnt exist (ie if its the first test run in a particular Test Class):

 public function startTest( test:Test ):void
    {
	   var suiteNode:XML = getSuiteNode(test)
	   suiteNode.appendChild(new XML(""));
	   __testTimer = getTimer();
    }

//------------------------------------------------------------------------------

private function getSuiteNode (test:Test):XML{
var outNode:XML = __resultXML.testsuite.(@name==test.className)[0];
if(outNode==null){
outNode = new XML("");
__resultXML.appendChild(outNode);
}
return outNode;
}


addError / addFailure


When a test fails or errors, we need to append failure details to the testcase node. The child node is named either failure or error depending on the type of problem (these nodes get displayed differently in CruiseControl). A child node populated with the stack trace is appended to the testcase node:



public function addError( test:Test, error:Error ):void{
onFailOrError(test,error,"error");
}
//------------------------------------------------------------------------------
public function addFailure( test:Test, error:AssertionFailedError ):void{
onFailOrError(test,error,"failure");
}

private function onFailOrError(test:Test,error:Error, failOrError:String):void{
__suiteSuccess = false;
var testNode:XML = getTestNode(test);
var childNode:XML = new XML("<"+failOrError+">"+error.getStackTrace()+"");
testNode.appendChild(childNode);
}
private function getTestNode(test:Test):XML{
return __resultXML.testsuite.testcase.(@name==TestCase(test).methodName)[0];
}


endTest


If you look at the startTest code, you will see that we set a __testTimer variable. This comes into play in the endTest callback, where we set the execution time of the test Case:


public function endTest( test:Test ):void
{
var testNode:XML = getTestNode(test);
testNode.@time = (getTimer() - __testTimer)/1000;
}

print


When the test suite is complete, the TestRunner calls the print function on our class. This is actually now a much simpler affair than the one I previously wrote for ASUnit (although I will be revisiting this for ASUnit for AS3 over the next few days...). It simply traces out our generated XML, and also includes the line at the bottom specifying test Suite success (this is read by our ANT build):

    
public function print( result:TestResult, runTime:Number ):void
{
printHeader(runTime);
printMain();
printFooter(result);
}
private function printHeader( runTime:Number ):void
{
trace("-----------------TESTRUNNEROUTPUTBEGINS----------------");
}
private function printMain():void{
trace(__resultXML);
}
private function printFooter( result:TestResult ):void
{
trace("Test Suite success: "+(result.errorCount()+result.failureCount()==0)+"\n");
trace("-----------------TESTRUNNEROUTPUTENDS----------------");
}

And that's basically it.. My next post is going to be about how I actually went about implementing these classes and got the test siute to display results differently depending on whether it was being run by the developer or as part of an ANT build...

Asyncronous test setup with flexunit

June 15, 2006

As part of my evaluation of flexunit and ASUnit for continuous integration, one of the rquirements is the ability to have an asynchronous test setup. Lets say you have a class under test that requires some XML in its constructor, and ideally you want to load that XML from an external source. While this doesnt work straight out of the box for flex unit, I found a very simple way to do this. Basically, Flexunit runs its TestCases by executingthe setUp method, then runMiddle() which runs the normal test method or the next asyncronous test. So the trick here is to get runMiddle to execute only when we are ready for it. This question had come up on the Flexcoders mailing list before, and Matt Chotin suggested a possible solution. Here's mine:

Basically, what you can do is override the runMiddle function to be an empty method, and then when you are ready to actually run your test—in my example here its when an XML file has loaded—simply call super.runMiddle()


override public function setUp():void{
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, completeHandler);
var request:URLRequest = new URLRequest("employee.xml");
loader.load(request);
}
private function completeHandler(event:Event):void {
var loader:URLLoader = URLLoader(event.target);
instance = new AsyncSetupTestClass(new XML(loader.data));
super.runMiddle();
}
override public function runMiddle():void{}

In my simple tests so far this seems to work, but if anyone notices anything out of whack, let me know...

Unit test frameworks for AS3 and Continuous Integration

June 15, 2006

Im currently evaluating FlexUnit and ASUnit as we move over to AS3 and seeing how they will fit in with our continuous integration suite. As you may have read in my previous posts on CI, we ended up significantly reworking ASUnit to get it to integrate with our needs for CI. What we are really looking for now is a framework we do not have to monkey around with too much to acheive our needs. The unit test framework we use needs to fulfil the following:


  • Simple syncronous tests

  • asynchronous tests

  • asynchronous setup (e.g. if you ned to load in some data before performing the tests)

  • printing the test result out to a log file so that Cruise Control can interpret the results

  • broadcast some sort of event when the test suite is complete so we can close the test harness and continue with the build

  • works equally well outside of the flex framework


I'll be looking at both FlexUnit and ASUnit to see which one will work for our needs best. Ideally I would like to offer up solutions to using both so that you can fit CI into any testing framework you are currently using. I'll post my findings as I find them, so stay posted...

« May 2006 | Main | July 2006 »

Syndicate

Add to Technorati Favorites Powered by
Movable Type 3.2

Holiday Fund