Continuous Integration with Flex – Part 3

So, if you have got this far, your ant build will compile the test harness, which quits after a specified period of time, and then compiles the main build. So far, so good, but it’s not really rocking yet; what we now need to do is get our ANT build to know if our unit tests have actually passed or not (and later down the line by the build monitor). The trick here is to get your swf to somehow output to a textfile that can then be read & understood by the ant build.

As Luke had already blogged, the easiest way to do this is to take advantage of the under-publicized fact that you can get trace output to write to a log file when using the Flash debug player. What you need to do is amend your mm.cfg file (which is found at the root of your user directory) to look something like this:

ProfilingOutputFileEnable=0
ProfilingOutputDirectory=C:Program FilesMacromediaFlexjrun4serversdefaultprofilerWEB-INFProfilerData
FrameProfilingEnable=0
ProfileFunctionEnable=1
ErrorReportingEnable=1
TraceOutputFileEnable=1
TraceOutputFileName= C:/logs/flashlog.txt

For our concerns, the most important two lines are the last two. We will use this outputted log to our advantage very soon… The next step is to get ASUnit to trace its output as well as send it to the ASUnit UI. I built a simple class called TraceOutput which is very similar to the class that prints results to the UI. the main difference is that the ASUnit UI refreshes its output at the completion of every unit test, whereas we really only want the output traced once. The TraceOutput class is given test results at the same time as the testRunner sends results to the UI. This was done by adding the following to the testRunner class:

/**
* @author Paul BH -- paul@eyefodder.com
*/
class com.asunit.util.TraceOutput {
	private var items : Array;
	private var version:String = "2.6.4";
	private var successes:Number = 0;
	private var failures:Number = 0;
	private static var instance : TraceOutput;
	public static function getInstance() : TraceOutput {
		if (instance == null)
			instance = new TraceOutput();
		return instance;
	}
	private function TraceOutput() {
	}
	public function printReport():Void{
		refreshOutput();
	}
	public function refreshOutput():Void {
		trace(getFirstLine());
		trace(getTestResultString());
		trace(getSuccessString((failures == 0)));
		printReportAsXML()
		trace(getLastLine());
	}
	public function addTest(item:Object, length:Number):Void {
		if(length == 1) {
			items = new Array();
		}
		items.push(item);
	}
	private function getSuccessString(isSuccess:Boolean):String{
		var str:String = "Test Suite success: "+isSuccess;
		str += "n";
		return str;
	}
	private function getVersionNumber():String {
		return version;
	}
	private function getFirstLine():String {
		var str:String = "-----------------TESTRUNNEROUTPUTBEGINS----------------";
		str += "nVersion : " + getVersionNumber() + "n";
		return str;
	}
	private function getLastLine():String{
		var str:String = "-----------------TESTRUNNEROUTPUTENDS----------------";
		return str;
	}
	public function getTestResultString():String {
		successes = 0;
		failures = 0;
		var str:String = "";
		var fStr:String = "";
		for(var i:Number=0; i<items.length; i++) {
			str += items[i].output;
			if(items[i].success) {
				successes++;
			} else {
				failures++;
				fStr += renderItemFailure(items[i]);
			}
		}
		return str + "n" + getTestNumberOutput() + "n" + fStr;
	}
	public function renderItemFailure(item:Object):String {
		var str:String = "-----------------nItem Failed at :n";
		str += "assertion : " + item.assertion + "n";
		str += "message : " + item.message + "n";
		str += "methodName : " + item.methodName + "n";
		str += "className : " + item.className + "n";
		return str;
	}
	public function getTestNumberOutput():String {
		return successes + " out of " + items.length + " Asserts passed.n";
	}
	public function printReportAsXML():Void{
		var str:String = getReportAsXML();
		trace (str);
	}
	private function getReportAsXML():String{
		var ind:String = "t";
		var nl:String = "n";
		var str:String = "<testsuites>"+nl;
		var suiteObj:Object = getItemsBySuite();
		for (var i in suiteObj){
			str += getSuiteAsXML(suiteObj[i],i);
		}
		str += "</testsuites>";
		return str;
	}
	private function getSuiteAsXML(arr:Array, suiteName:String):String{
		var outStr = "";
		var ind:String = "t";
		var nl:String = "n";
		var max:Number = arr.length;
		var time:Number = 0;
		var tests:Number = max;
		var failures:Number = 0;
		var item:Object;
		for (var j:Number=0;j<max;++j){
			item = arr[j];
			time += item.executionTime;
			failures += item.success ? 0 : 1;
			outStr += ind+ind+getTestCaseAsXML(item)+nl;
		}
		var openingTag:String = "<testsuite name=""+suiteName+""";
		openingTag += " failures=""+failures+""";
		openingTag += " tests=""+tests+""";
		openingTag += " time=""+time+""";
		openingTag += ">";
		outStr = ind+openingTag+nl+outStr;
		outStr += ind+"</testsuite>"+nl;
		return outStr;
	}
	private function getItemsBySuite():Object{
		var outObj:Object = {};
		var arr:Array = items;
		var max:Number = arr.length;
		for (var i:Number=0;i<max;++i){
			var ref:String = arr[i].className;
			if(outObj[ref]==undefined){
				outObj[ref] = [];
			}
			outObj[ref].push(arr[i]);
		}
		return outObj;
	}
	private function getTestCaseAsXML(inObj):String{
		var str:String = "<testcase name=""+inObj.methodName+"" time=""+inObj.executionTime+"">";
		if (inObj.success==false){
			str += "<failure>"+inObj.message+"</failure>";
		}
		str += "</testcase>";
		return str;
	}
}

Now, in order to get the asunit framework to actually add results to the TraceOutput class, we need to add one line to the renderTest method of com.asunit.framework.TestRunner :

public function renderTest(item:Object):Void {
	var lc:LocalConnClient = getLocalConn();
	lc["addTest"](item, length);
	TraceOutput.getInstance().addTest(item,length);
}

Next we want the class to print its results in the trace panel, so add the following line of code to your test harness file:

private function onTestsComplete(){
	TraceOutput.printTestResults();
	fscommand(“quit”);
}

Now, when you run your test output, you will see test results in your output panel (we use flashlog in Eclipse). something like this:

-----------------TESTRUNNEROUTPUTBEGINS----------------
Version : FMR 3.1.1
.F
1 out of 2 Asserts passed.
-----------------
Item Failed:
assertion : assertTrue
message : failingtest
methodName : test
className : com.TestClassTest
Test Suite success: false
<testsuites>
<testsuite name="com.TestClassTest" failures="1" tests="2" time="0.008">
<testcase name="testInstantiated" time="0.008"></testcase>
<testcase name="test" time="0"><failure>failingtest</failure></testcase>
</testsuite>
</testsuites>
-----------------TESTRUNNEROUTPUTENDS----------------

As you can see, there are two parts to the output. The first is the nice, human readable output, modeled very closely on the output in the ASUnit UI. The only real change to it is that instead of the red bar/ green bar for test suite result, I changed this to say Test suite success: true | false. This will prove useful in the next step when ANT needs to determine test suite success / failure. The second half is the test output printed out as a chunk of XML. This will get used by our build tool later down the line… In my next post, we will delve a little into python land to get the flash log parsed into a usable state for our build tool….

This entry was posted in Continuous Integration, Flash & Actionscript. Bookmark the permalink. Comments are closed, but you can leave a trackback: Trackback URL.