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()+"<!--"+failOrError+"-->"); 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; }
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…
Paul,
There could be some possible trouble and confusion here for anyone just trying to download the files and go. They will find a dead link when they try to download the CruiseControlTestPrinter.as file.
CruiseControlTestPrinter should actually be CruiseControlResultPrinter as referenced in CruiseControlTestRunner.as
The link for CruiseControlResultPrinter.as should be found at http://www.eyefodder.com/blog/downloads/CruiseControlResultPrinter.as
Thanks for the great reference. Glad to see others working on TDD and CI in Flex.
_Kris
thanks for pointing this out Kris – made the change, so it should work now
Thanks for these blogs Paul,
I was very new to CI and you’ve speeded up the whole process for me integrating CI with Flex!
One thing I did notice when using the CruiseControlResultPrinter, I had to change the format of the string from the StackTrace when a failure occurs as the text would have ” around the values and this effected the XML being generated which caused an error and broke the build. However with this removed it all runs like a dream.
Thanks again!!
Francesca