Mercurial > hg > audiodb
diff bindings/as3/ext/asunit/framework/TestCase.as @ 732:3a0b9700b3d2
* Initial AS3 commit
author | mas01mj |
---|---|
date | Tue, 14 Sep 2010 16:47:10 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bindings/as3/ext/asunit/framework/TestCase.as Tue Sep 14 16:47:10 2010 +0000 @@ -0,0 +1,487 @@ +package asunit.framework { + import flash.display.DisplayObject; + import flash.display.DisplayObjectContainer; + import flash.errors.IllegalOperationError; + import flash.events.Event; + import flash.utils.describeType; + import flash.utils.getDefinitionByName; + import flash.utils.setTimeout; + + import asunit.errors.AssertionFailedError; + import asunit.util.ArrayIterator; + import asunit.util.Iterator; + + /** + * A test case defines the fixture to run multiple tests. To define a test case<br> + * 1) implement a subclass of TestCase<br> + * 2) define instance variables that store the state of the fixture<br> + * 3) initialize the fixture state by overriding <code>setUp</code><br> + * 4) clean-up after a test by overriding <code>tearDown</code>.<br> + * Each test runs in its own fixture so there + * can be no side effects among test runs. + * Here is an example: + * <listing> + * public class MathTest extends TestCase { + * private var value1:Number; + * private var value2:Number; + * + * public function MathTest(methodName:String=null) { + * super(methodName); + * } + * + * override protected function setUp():void { + * super.setUp(); + * value1 = 2; + * value2 = 3; + * } + * } + * </listing> + * + * For each test implement a method which interacts + * with the fixture. Verify the expected results with assertions specified + * by calling <code>assertTrue</code> with a boolean, or <code>assertEquals</code> + * with two primitive values that should match. + * <listing> + * public function testAdd():void { + * var result:Number = value1 + value2; + * assertEquals(5, result); + * } + * </listing> + * + * There are three common types of test cases: + * + * <ol> + * <li>Simple unit test</li> + * <li>Visual integration test</li> + * <li>Asynchronous test</li> + * </ol> + * + * @includeExample MathUtilTest.as + * @includeExample ComponentTestIntroduction.as + * @includeExample ComponentUnderTest.as + * @includeExample ComponentTestExample.as + * @includeExample AsynchronousTestMethodExample.as + */ + public class TestCase extends Assert implements Test { + protected static const PRE_SET_UP:int = 0; + protected static const SET_UP:int = 1; + protected static const RUN_METHOD:int = 2; + protected static const TEAR_DOWN:int = 3; + protected static const DEFAULT_TIMEOUT:int = 1000; + + protected var context:DisplayObjectContainer; + protected var fName:String; + protected var isComplete:Boolean; + protected var result:TestListener; + protected var testMethods:Array; + + private var asyncQueue:Array; + private var currentMethod:String; + private var currentState:int; + private var layoutManager:Object; + private var methodIterator:Iterator; + private var runSingle:Boolean; + + /** + * Constructs a test case with the given name. + * + * Be sure to implement the constructor in your own TestCase base classes. + * + * Using the optional <code>testMethod</code> constructor parameter is how we + * create and run a single test case and test method. + */ + public function TestCase(testMethod:String = null) { + var description:XML = describeType(this); + var className:Object = description.@name; + var methods:XMLList = description..method.((@name+"").match("^test")); + if(testMethod != null) { + testMethods = testMethod.split(", ").join(",").split(","); + if(testMethods.length == 1) { + runSingle = true; + } + } else { + setTestMethods(methods); + } + setName(className.toString()); + resolveLayoutManager(); + asyncQueue = []; + } + + private function resolveLayoutManager():void { + // Avoid creating import dependencies on flex framework + // If you have the framework.swc in your classpath, + // the layout manager will be found, if not, a mcok + // will be used. + try { + var manager:Class = getDefinitionByName("mx.managers.LayoutManager") as Class; + layoutManager = manager["getInstance"](); + if(!layoutManager.hasOwnProperty("resetAll")) { + throw new Error("TestCase :: mx.managers.LayoutManager missing resetAll method"); + } + } + catch(e:Error) { + layoutManager = new Object(); + layoutManager.resetAll = function():void { + }; + } + } + + /** + * Sets the name of a TestCase + * @param name The name to set + */ + public function setName(name:String):void { + fName = name; + } + + protected function setTestMethods(methodNodes:XMLList):void { + testMethods = new Array(); + var methodNames:Object = methodNodes.@name; + var name:String; + for each(var item:Object in methodNames) { + name = item.toString(); + testMethods.push(name); + } + } + + public function getTestMethods():Array { + return testMethods; + } + + /** + * Counts the number of test cases executed by run(TestResult result). + */ + public function countTestCases():int { + return testMethods.length; + } + + /** + * Creates a default TestResult object + * + * @see TestResult + */ + protected function createResult():TestResult { + return new TestResult(); + } + + /** + * A convenience method to run this test, collecting the results with + * either the TestResult provided or a default, new TestResult object. + * Expects either: + * run():void // will return the newly created TestResult + * run(result:TestResult):TestResult // will use the TestResult + * that was passed in. + * + * @see TestResult + */ + public function run():void { + getResult().run(this); + } + + public function setResult(result:TestListener):void { + this.result = result; + } + + internal function getResult():TestListener { + return (result == null) ? createResult() : result; + } + + /** + * Runs the bare test sequence. + * @throws Error if any exception is thrown + */ + public function runBare():void { + if(isComplete) { + return; + } + var name:String; + var itr:Iterator = getMethodIterator(); + if(itr.hasNext()) { + name = String(itr.next()); + currentState = PRE_SET_UP; + runMethod(name); + } + else { + cleanUp(); + getResult().endTest(this); + isComplete = true; + dispatchEvent(new Event(Event.COMPLETE)); + } + } + + private function getMethodIterator():Iterator { + if(methodIterator == null) { + methodIterator = new ArrayIterator(testMethods); + } + return methodIterator; + } + + /** + * Override this method in Asynchronous test cases + * or any other time you want to perform additional + * member cleanup after all test methods have run + **/ + protected function cleanUp():void { + } + + private function runMethod(methodName:String):void { + try { + if(currentState == PRE_SET_UP) { + currentState = SET_UP; + getResult().startTestMethod(this, methodName); + setUp(); // setUp may be async and change the state of methodIsAsynchronous + } + currentMethod = methodName; + if(!waitForAsync()) { + currentState = RUN_METHOD; + this[methodName](); + } + } + catch(assertionFailedError:AssertionFailedError) { + getResult().addFailure(this, assertionFailedError); + } + catch(unknownError:Error) { + getResult().addError(this, unknownError); + } + finally { + if(!waitForAsync()) { + runTearDown(); + } + } + } + + /** + * Sets up the fixture, for example, instantiate a mock object. + * This method is called before each test is executed. + * throws Exception on error. + * + * @example This method is usually overridden in your concrete test cases: + * <listing> + * private var instance:MyInstance; + * + * override protected function setUp():void { + * super.setUp(); + * instance = new MyInstance(); + * addChild(instance); + * } + * </listing> + */ + protected function setUp():void { + } + /** + * Tears down the fixture, for example, delete mock object. + * + * This method is called after a test is executed - even if the test method + * throws an exception or fails. + * + * Even though the base class <code>TestCase</code> doesn't do anything on <code>tearDown</code>, + * It's a good idea to call <code>super.tearDown()</code> in your subclasses. Many projects + * wind up using some common fixtures which can often be extracted out a common project + * <code>TestCase</code>. + * + * <code>tearDown</code> is <em>not</em> called when we tell a test case to execute + * a single test method. + * + * @throws Error on error. + * + * @example This method is usually overridden in your concrete test cases: + * <listing> + * private var instance:MyInstance; + * + * override protected function setUp():void { + * super.setUp(); + * instance = new MyInstance(); + * addChild(instance); + * } + * + * override protected function tearDown():void { + * super.tearDown(); + * removeChild(instance); + * } + * </listing> + * + */ + protected function tearDown():void { + } + + /** + * Returns a string representation of the test case + */ + override public function toString():String { + if(getCurrentMethod()) { + return getName() + "." + getCurrentMethod() + "()"; + } + else { + return getName(); + } + } + /** + * Gets the name of a TestCase + * @return returns a String + */ + public function getName():String { + return fName; + } + + public function getCurrentMethod():String { + return currentMethod; + } + + public function getIsComplete():Boolean { + return isComplete; + } + + public function setContext(context:DisplayObjectContainer):void { + this.context = context; + } + + /** + * Returns the visual <code>DisplayObjectContainer</code> that will be used by + * <code>addChild</code> and <code>removeChild</code> helper methods. + **/ + public function getContext():DisplayObjectContainer { + return context; + } + + /** + * Called from within <code>setUp</code> or the body of any test method. + * + * Any call to <code>addAsync</code>, will prevent test execution from continuing + * until the <code>duration</code> (in milliseconds) is exceeded, or the function returned by <code>addAsync</code> + * is called. <code>addAsync</code> can be called any number of times within a particular + * test method, and will block execution until each handler has returned. + * + * Following is an example of how to use the <code>addAsync</code> feature: + * <listing> + * public function testDispatcher():void { + * var dispatcher:IEventDispatcher = new EventDispatcher(); + * // Subscribe to an event by sending the return value of addAsync: + * dispatcher.addEventListener(Event.COMPLETE, addAsync(function(event:Event):void { + * // Make assertions *inside* your async handler: + * assertEquals(34, dispatcher.value); + * })); + * } + * </listing> + * + * If you just want to verify that a particular event is triggered, you don't + * need to provide a handler of your own, you can do the following: + * <listing> + * public function testDispatcher():void { + * var dispatcher:IEventDispatcher = new EventDispatcher(); + * dispatcher.addEventListener(Event.COMPLETE, addAsync()); + * } + * </listing> + * + * If you have a series of events that need to happen, you can generally add + * the async handler to the last one. + * + * The main thing to remember is that any assertions that happen outside of the + * initial thread of execution, must be inside of an <code>addAsync</code> block. + **/ + protected function addAsync(handler:Function = null, duration:Number=DEFAULT_TIMEOUT, failureHandler:Function=null):Function { + if(handler == null) { + handler = function(args:*):* {return;}; + } + var async:AsyncOperation = new AsyncOperation(this, handler, duration, failureHandler); + asyncQueue.push(async); + return async.getCallback(); + } + + internal function asyncOperationTimeout(async:AsyncOperation, duration:Number, isError:Boolean=true):void { + if(isError) getResult().addError(this, new IllegalOperationError("TestCase.timeout (" + duration + "ms) exceeded on an asynchronous operation.")); + asyncOperationComplete(async); + } + + internal function asyncOperationComplete(async:AsyncOperation):void{ + // remove operation from queue + var i:int = asyncQueue.indexOf(async); + asyncQueue.splice(i,1); + // if we still need to wait, return + if(waitForAsync()) return; + if(currentState == SET_UP) { + runMethod(currentMethod); + } + else if(currentState == RUN_METHOD) { + runTearDown(); + } + } + + private function waitForAsync():Boolean{ + return asyncQueue.length > 0; + } + + protected function runTearDown():void { + if(currentState == TEAR_DOWN) { + return; + } + currentState = TEAR_DOWN; + if(isComplete) { + return; + } + if(!runSingle) { + getResult().endTestMethod(this, currentMethod); + tearDown(); + layoutManager.resetAll(); + } + setTimeout(runBare, 5); + } + + /** + * Helper method for testing <code>DisplayObject</code>s. + * + * This method allows you to more easily add and manage <code>DisplayObject</code> + * instances in your <code>TestCase</code>. + * + * If you are using the regular <code>TestRunner</code>, you cannot add Flex classes. + * + * If you are using a <code>FlexRunner</code> base class, you can add either + * regular <code>DisplayObject</code>s or <code>IUIComponent</code>s. + * + * Usually, this method is called within <code>setUp</code>, and <code>removeChild</code> + * is called from within <code>tearDown</code>. Using these methods, ensures that added + * children will be subsequently removed, even when tests fail. + * + * Here is an example of the <code>addChild</code> method: + * <listing> + * private var instance:MyComponent; + * + * override protected function setUp():void { + * super.setUp(); + * instance = new MyComponent(); + * instance.addEventListener(Event.COMPLETE, addAsync()); + * addChild(instance); + * } + * + * override protected function tearDown():void { + * super.tearDown(); + * removeChild(instance); + * } + * + * public function testParam():void { + * assertEquals(34, instance.value); + * } + * </listing> + **/ + protected function addChild(child:DisplayObject):DisplayObject { + return getContext().addChild(child); + } + + /** + * Helper method for removing added <code>DisplayObject</code>s. + * + * <b>Update:</b> This method should no longer fail if the provided <code>DisplayObject</code> + * has already been removed. + **/ + protected function removeChild(child:DisplayObject):DisplayObject { + if(child == null) { + return null; + } + try { + return getContext().removeChild(child); + } + catch(e:Error) { + } + return null; + } + } +}