annotate bindings/as3/ext/asunit/framework/TestCase.as @ 770:c54bc2ffbf92 tip

update tags
author convert-repo
date Fri, 16 Dec 2011 11:34:01 +0000
parents 3a0b9700b3d2
children
rev   line source
mas01mj@732 1 package asunit.framework {
mas01mj@732 2 import flash.display.DisplayObject;
mas01mj@732 3 import flash.display.DisplayObjectContainer;
mas01mj@732 4 import flash.errors.IllegalOperationError;
mas01mj@732 5 import flash.events.Event;
mas01mj@732 6 import flash.utils.describeType;
mas01mj@732 7 import flash.utils.getDefinitionByName;
mas01mj@732 8 import flash.utils.setTimeout;
mas01mj@732 9
mas01mj@732 10 import asunit.errors.AssertionFailedError;
mas01mj@732 11 import asunit.util.ArrayIterator;
mas01mj@732 12 import asunit.util.Iterator;
mas01mj@732 13
mas01mj@732 14 /**
mas01mj@732 15 * A test case defines the fixture to run multiple tests. To define a test case<br>
mas01mj@732 16 * 1) implement a subclass of TestCase<br>
mas01mj@732 17 * 2) define instance variables that store the state of the fixture<br>
mas01mj@732 18 * 3) initialize the fixture state by overriding <code>setUp</code><br>
mas01mj@732 19 * 4) clean-up after a test by overriding <code>tearDown</code>.<br>
mas01mj@732 20 * Each test runs in its own fixture so there
mas01mj@732 21 * can be no side effects among test runs.
mas01mj@732 22 * Here is an example:
mas01mj@732 23 * <listing>
mas01mj@732 24 * public class MathTest extends TestCase {
mas01mj@732 25 * private var value1:Number;
mas01mj@732 26 * private var value2:Number;
mas01mj@732 27 *
mas01mj@732 28 * public function MathTest(methodName:String=null) {
mas01mj@732 29 * super(methodName);
mas01mj@732 30 * }
mas01mj@732 31 *
mas01mj@732 32 * override protected function setUp():void {
mas01mj@732 33 * super.setUp();
mas01mj@732 34 * value1 = 2;
mas01mj@732 35 * value2 = 3;
mas01mj@732 36 * }
mas01mj@732 37 * }
mas01mj@732 38 * </listing>
mas01mj@732 39 *
mas01mj@732 40 * For each test implement a method which interacts
mas01mj@732 41 * with the fixture. Verify the expected results with assertions specified
mas01mj@732 42 * by calling <code>assertTrue</code> with a boolean, or <code>assertEquals</code>
mas01mj@732 43 * with two primitive values that should match.
mas01mj@732 44 * <listing>
mas01mj@732 45 * public function testAdd():void {
mas01mj@732 46 * var result:Number = value1 + value2;
mas01mj@732 47 * assertEquals(5, result);
mas01mj@732 48 * }
mas01mj@732 49 * </listing>
mas01mj@732 50 *
mas01mj@732 51 * There are three common types of test cases:
mas01mj@732 52 *
mas01mj@732 53 * <ol>
mas01mj@732 54 * <li>Simple unit test</li>
mas01mj@732 55 * <li>Visual integration test</li>
mas01mj@732 56 * <li>Asynchronous test</li>
mas01mj@732 57 * </ol>
mas01mj@732 58 *
mas01mj@732 59 * @includeExample MathUtilTest.as
mas01mj@732 60 * @includeExample ComponentTestIntroduction.as
mas01mj@732 61 * @includeExample ComponentUnderTest.as
mas01mj@732 62 * @includeExample ComponentTestExample.as
mas01mj@732 63 * @includeExample AsynchronousTestMethodExample.as
mas01mj@732 64 */
mas01mj@732 65 public class TestCase extends Assert implements Test {
mas01mj@732 66 protected static const PRE_SET_UP:int = 0;
mas01mj@732 67 protected static const SET_UP:int = 1;
mas01mj@732 68 protected static const RUN_METHOD:int = 2;
mas01mj@732 69 protected static const TEAR_DOWN:int = 3;
mas01mj@732 70 protected static const DEFAULT_TIMEOUT:int = 1000;
mas01mj@732 71
mas01mj@732 72 protected var context:DisplayObjectContainer;
mas01mj@732 73 protected var fName:String;
mas01mj@732 74 protected var isComplete:Boolean;
mas01mj@732 75 protected var result:TestListener;
mas01mj@732 76 protected var testMethods:Array;
mas01mj@732 77
mas01mj@732 78 private var asyncQueue:Array;
mas01mj@732 79 private var currentMethod:String;
mas01mj@732 80 private var currentState:int;
mas01mj@732 81 private var layoutManager:Object;
mas01mj@732 82 private var methodIterator:Iterator;
mas01mj@732 83 private var runSingle:Boolean;
mas01mj@732 84
mas01mj@732 85 /**
mas01mj@732 86 * Constructs a test case with the given name.
mas01mj@732 87 *
mas01mj@732 88 * Be sure to implement the constructor in your own TestCase base classes.
mas01mj@732 89 *
mas01mj@732 90 * Using the optional <code>testMethod</code> constructor parameter is how we
mas01mj@732 91 * create and run a single test case and test method.
mas01mj@732 92 */
mas01mj@732 93 public function TestCase(testMethod:String = null) {
mas01mj@732 94 var description:XML = describeType(this);
mas01mj@732 95 var className:Object = description.@name;
mas01mj@732 96 var methods:XMLList = description..method.((@name+"").match("^test"));
mas01mj@732 97 if(testMethod != null) {
mas01mj@732 98 testMethods = testMethod.split(", ").join(",").split(",");
mas01mj@732 99 if(testMethods.length == 1) {
mas01mj@732 100 runSingle = true;
mas01mj@732 101 }
mas01mj@732 102 } else {
mas01mj@732 103 setTestMethods(methods);
mas01mj@732 104 }
mas01mj@732 105 setName(className.toString());
mas01mj@732 106 resolveLayoutManager();
mas01mj@732 107 asyncQueue = [];
mas01mj@732 108 }
mas01mj@732 109
mas01mj@732 110 private function resolveLayoutManager():void {
mas01mj@732 111 // Avoid creating import dependencies on flex framework
mas01mj@732 112 // If you have the framework.swc in your classpath,
mas01mj@732 113 // the layout manager will be found, if not, a mcok
mas01mj@732 114 // will be used.
mas01mj@732 115 try {
mas01mj@732 116 var manager:Class = getDefinitionByName("mx.managers.LayoutManager") as Class;
mas01mj@732 117 layoutManager = manager["getInstance"]();
mas01mj@732 118 if(!layoutManager.hasOwnProperty("resetAll")) {
mas01mj@732 119 throw new Error("TestCase :: mx.managers.LayoutManager missing resetAll method");
mas01mj@732 120 }
mas01mj@732 121 }
mas01mj@732 122 catch(e:Error) {
mas01mj@732 123 layoutManager = new Object();
mas01mj@732 124 layoutManager.resetAll = function():void {
mas01mj@732 125 };
mas01mj@732 126 }
mas01mj@732 127 }
mas01mj@732 128
mas01mj@732 129 /**
mas01mj@732 130 * Sets the name of a TestCase
mas01mj@732 131 * @param name The name to set
mas01mj@732 132 */
mas01mj@732 133 public function setName(name:String):void {
mas01mj@732 134 fName = name;
mas01mj@732 135 }
mas01mj@732 136
mas01mj@732 137 protected function setTestMethods(methodNodes:XMLList):void {
mas01mj@732 138 testMethods = new Array();
mas01mj@732 139 var methodNames:Object = methodNodes.@name;
mas01mj@732 140 var name:String;
mas01mj@732 141 for each(var item:Object in methodNames) {
mas01mj@732 142 name = item.toString();
mas01mj@732 143 testMethods.push(name);
mas01mj@732 144 }
mas01mj@732 145 }
mas01mj@732 146
mas01mj@732 147 public function getTestMethods():Array {
mas01mj@732 148 return testMethods;
mas01mj@732 149 }
mas01mj@732 150
mas01mj@732 151 /**
mas01mj@732 152 * Counts the number of test cases executed by run(TestResult result).
mas01mj@732 153 */
mas01mj@732 154 public function countTestCases():int {
mas01mj@732 155 return testMethods.length;
mas01mj@732 156 }
mas01mj@732 157
mas01mj@732 158 /**
mas01mj@732 159 * Creates a default TestResult object
mas01mj@732 160 *
mas01mj@732 161 * @see TestResult
mas01mj@732 162 */
mas01mj@732 163 protected function createResult():TestResult {
mas01mj@732 164 return new TestResult();
mas01mj@732 165 }
mas01mj@732 166
mas01mj@732 167 /**
mas01mj@732 168 * A convenience method to run this test, collecting the results with
mas01mj@732 169 * either the TestResult provided or a default, new TestResult object.
mas01mj@732 170 * Expects either:
mas01mj@732 171 * run():void // will return the newly created TestResult
mas01mj@732 172 * run(result:TestResult):TestResult // will use the TestResult
mas01mj@732 173 * that was passed in.
mas01mj@732 174 *
mas01mj@732 175 * @see TestResult
mas01mj@732 176 */
mas01mj@732 177 public function run():void {
mas01mj@732 178 getResult().run(this);
mas01mj@732 179 }
mas01mj@732 180
mas01mj@732 181 public function setResult(result:TestListener):void {
mas01mj@732 182 this.result = result;
mas01mj@732 183 }
mas01mj@732 184
mas01mj@732 185 internal function getResult():TestListener {
mas01mj@732 186 return (result == null) ? createResult() : result;
mas01mj@732 187 }
mas01mj@732 188
mas01mj@732 189 /**
mas01mj@732 190 * Runs the bare test sequence.
mas01mj@732 191 * @throws Error if any exception is thrown
mas01mj@732 192 */
mas01mj@732 193 public function runBare():void {
mas01mj@732 194 if(isComplete) {
mas01mj@732 195 return;
mas01mj@732 196 }
mas01mj@732 197 var name:String;
mas01mj@732 198 var itr:Iterator = getMethodIterator();
mas01mj@732 199 if(itr.hasNext()) {
mas01mj@732 200 name = String(itr.next());
mas01mj@732 201 currentState = PRE_SET_UP;
mas01mj@732 202 runMethod(name);
mas01mj@732 203 }
mas01mj@732 204 else {
mas01mj@732 205 cleanUp();
mas01mj@732 206 getResult().endTest(this);
mas01mj@732 207 isComplete = true;
mas01mj@732 208 dispatchEvent(new Event(Event.COMPLETE));
mas01mj@732 209 }
mas01mj@732 210 }
mas01mj@732 211
mas01mj@732 212 private function getMethodIterator():Iterator {
mas01mj@732 213 if(methodIterator == null) {
mas01mj@732 214 methodIterator = new ArrayIterator(testMethods);
mas01mj@732 215 }
mas01mj@732 216 return methodIterator;
mas01mj@732 217 }
mas01mj@732 218
mas01mj@732 219 /**
mas01mj@732 220 * Override this method in Asynchronous test cases
mas01mj@732 221 * or any other time you want to perform additional
mas01mj@732 222 * member cleanup after all test methods have run
mas01mj@732 223 **/
mas01mj@732 224 protected function cleanUp():void {
mas01mj@732 225 }
mas01mj@732 226
mas01mj@732 227 private function runMethod(methodName:String):void {
mas01mj@732 228 try {
mas01mj@732 229 if(currentState == PRE_SET_UP) {
mas01mj@732 230 currentState = SET_UP;
mas01mj@732 231 getResult().startTestMethod(this, methodName);
mas01mj@732 232 setUp(); // setUp may be async and change the state of methodIsAsynchronous
mas01mj@732 233 }
mas01mj@732 234 currentMethod = methodName;
mas01mj@732 235 if(!waitForAsync()) {
mas01mj@732 236 currentState = RUN_METHOD;
mas01mj@732 237 this[methodName]();
mas01mj@732 238 }
mas01mj@732 239 }
mas01mj@732 240 catch(assertionFailedError:AssertionFailedError) {
mas01mj@732 241 getResult().addFailure(this, assertionFailedError);
mas01mj@732 242 }
mas01mj@732 243 catch(unknownError:Error) {
mas01mj@732 244 getResult().addError(this, unknownError);
mas01mj@732 245 }
mas01mj@732 246 finally {
mas01mj@732 247 if(!waitForAsync()) {
mas01mj@732 248 runTearDown();
mas01mj@732 249 }
mas01mj@732 250 }
mas01mj@732 251 }
mas01mj@732 252
mas01mj@732 253 /**
mas01mj@732 254 * Sets up the fixture, for example, instantiate a mock object.
mas01mj@732 255 * This method is called before each test is executed.
mas01mj@732 256 * throws Exception on error.
mas01mj@732 257 *
mas01mj@732 258 * @example This method is usually overridden in your concrete test cases:
mas01mj@732 259 * <listing>
mas01mj@732 260 * private var instance:MyInstance;
mas01mj@732 261 *
mas01mj@732 262 * override protected function setUp():void {
mas01mj@732 263 * super.setUp();
mas01mj@732 264 * instance = new MyInstance();
mas01mj@732 265 * addChild(instance);
mas01mj@732 266 * }
mas01mj@732 267 * </listing>
mas01mj@732 268 */
mas01mj@732 269 protected function setUp():void {
mas01mj@732 270 }
mas01mj@732 271 /**
mas01mj@732 272 * Tears down the fixture, for example, delete mock object.
mas01mj@732 273 *
mas01mj@732 274 * This method is called after a test is executed - even if the test method
mas01mj@732 275 * throws an exception or fails.
mas01mj@732 276 *
mas01mj@732 277 * Even though the base class <code>TestCase</code> doesn't do anything on <code>tearDown</code>,
mas01mj@732 278 * It's a good idea to call <code>super.tearDown()</code> in your subclasses. Many projects
mas01mj@732 279 * wind up using some common fixtures which can often be extracted out a common project
mas01mj@732 280 * <code>TestCase</code>.
mas01mj@732 281 *
mas01mj@732 282 * <code>tearDown</code> is <em>not</em> called when we tell a test case to execute
mas01mj@732 283 * a single test method.
mas01mj@732 284 *
mas01mj@732 285 * @throws Error on error.
mas01mj@732 286 *
mas01mj@732 287 * @example This method is usually overridden in your concrete test cases:
mas01mj@732 288 * <listing>
mas01mj@732 289 * private var instance:MyInstance;
mas01mj@732 290 *
mas01mj@732 291 * override protected function setUp():void {
mas01mj@732 292 * super.setUp();
mas01mj@732 293 * instance = new MyInstance();
mas01mj@732 294 * addChild(instance);
mas01mj@732 295 * }
mas01mj@732 296 *
mas01mj@732 297 * override protected function tearDown():void {
mas01mj@732 298 * super.tearDown();
mas01mj@732 299 * removeChild(instance);
mas01mj@732 300 * }
mas01mj@732 301 * </listing>
mas01mj@732 302 *
mas01mj@732 303 */
mas01mj@732 304 protected function tearDown():void {
mas01mj@732 305 }
mas01mj@732 306
mas01mj@732 307 /**
mas01mj@732 308 * Returns a string representation of the test case
mas01mj@732 309 */
mas01mj@732 310 override public function toString():String {
mas01mj@732 311 if(getCurrentMethod()) {
mas01mj@732 312 return getName() + "." + getCurrentMethod() + "()";
mas01mj@732 313 }
mas01mj@732 314 else {
mas01mj@732 315 return getName();
mas01mj@732 316 }
mas01mj@732 317 }
mas01mj@732 318 /**
mas01mj@732 319 * Gets the name of a TestCase
mas01mj@732 320 * @return returns a String
mas01mj@732 321 */
mas01mj@732 322 public function getName():String {
mas01mj@732 323 return fName;
mas01mj@732 324 }
mas01mj@732 325
mas01mj@732 326 public function getCurrentMethod():String {
mas01mj@732 327 return currentMethod;
mas01mj@732 328 }
mas01mj@732 329
mas01mj@732 330 public function getIsComplete():Boolean {
mas01mj@732 331 return isComplete;
mas01mj@732 332 }
mas01mj@732 333
mas01mj@732 334 public function setContext(context:DisplayObjectContainer):void {
mas01mj@732 335 this.context = context;
mas01mj@732 336 }
mas01mj@732 337
mas01mj@732 338 /**
mas01mj@732 339 * Returns the visual <code>DisplayObjectContainer</code> that will be used by
mas01mj@732 340 * <code>addChild</code> and <code>removeChild</code> helper methods.
mas01mj@732 341 **/
mas01mj@732 342 public function getContext():DisplayObjectContainer {
mas01mj@732 343 return context;
mas01mj@732 344 }
mas01mj@732 345
mas01mj@732 346 /**
mas01mj@732 347 * Called from within <code>setUp</code> or the body of any test method.
mas01mj@732 348 *
mas01mj@732 349 * Any call to <code>addAsync</code>, will prevent test execution from continuing
mas01mj@732 350 * until the <code>duration</code> (in milliseconds) is exceeded, or the function returned by <code>addAsync</code>
mas01mj@732 351 * is called. <code>addAsync</code> can be called any number of times within a particular
mas01mj@732 352 * test method, and will block execution until each handler has returned.
mas01mj@732 353 *
mas01mj@732 354 * Following is an example of how to use the <code>addAsync</code> feature:
mas01mj@732 355 * <listing>
mas01mj@732 356 * public function testDispatcher():void {
mas01mj@732 357 * var dispatcher:IEventDispatcher = new EventDispatcher();
mas01mj@732 358 * // Subscribe to an event by sending the return value of addAsync:
mas01mj@732 359 * dispatcher.addEventListener(Event.COMPLETE, addAsync(function(event:Event):void {
mas01mj@732 360 * // Make assertions *inside* your async handler:
mas01mj@732 361 * assertEquals(34, dispatcher.value);
mas01mj@732 362 * }));
mas01mj@732 363 * }
mas01mj@732 364 * </listing>
mas01mj@732 365 *
mas01mj@732 366 * If you just want to verify that a particular event is triggered, you don't
mas01mj@732 367 * need to provide a handler of your own, you can do the following:
mas01mj@732 368 * <listing>
mas01mj@732 369 * public function testDispatcher():void {
mas01mj@732 370 * var dispatcher:IEventDispatcher = new EventDispatcher();
mas01mj@732 371 * dispatcher.addEventListener(Event.COMPLETE, addAsync());
mas01mj@732 372 * }
mas01mj@732 373 * </listing>
mas01mj@732 374 *
mas01mj@732 375 * If you have a series of events that need to happen, you can generally add
mas01mj@732 376 * the async handler to the last one.
mas01mj@732 377 *
mas01mj@732 378 * The main thing to remember is that any assertions that happen outside of the
mas01mj@732 379 * initial thread of execution, must be inside of an <code>addAsync</code> block.
mas01mj@732 380 **/
mas01mj@732 381 protected function addAsync(handler:Function = null, duration:Number=DEFAULT_TIMEOUT, failureHandler:Function=null):Function {
mas01mj@732 382 if(handler == null) {
mas01mj@732 383 handler = function(args:*):* {return;};
mas01mj@732 384 }
mas01mj@732 385 var async:AsyncOperation = new AsyncOperation(this, handler, duration, failureHandler);
mas01mj@732 386 asyncQueue.push(async);
mas01mj@732 387 return async.getCallback();
mas01mj@732 388 }
mas01mj@732 389
mas01mj@732 390 internal function asyncOperationTimeout(async:AsyncOperation, duration:Number, isError:Boolean=true):void {
mas01mj@732 391 if(isError) getResult().addError(this, new IllegalOperationError("TestCase.timeout (" + duration + "ms) exceeded on an asynchronous operation."));
mas01mj@732 392 asyncOperationComplete(async);
mas01mj@732 393 }
mas01mj@732 394
mas01mj@732 395 internal function asyncOperationComplete(async:AsyncOperation):void{
mas01mj@732 396 // remove operation from queue
mas01mj@732 397 var i:int = asyncQueue.indexOf(async);
mas01mj@732 398 asyncQueue.splice(i,1);
mas01mj@732 399 // if we still need to wait, return
mas01mj@732 400 if(waitForAsync()) return;
mas01mj@732 401 if(currentState == SET_UP) {
mas01mj@732 402 runMethod(currentMethod);
mas01mj@732 403 }
mas01mj@732 404 else if(currentState == RUN_METHOD) {
mas01mj@732 405 runTearDown();
mas01mj@732 406 }
mas01mj@732 407 }
mas01mj@732 408
mas01mj@732 409 private function waitForAsync():Boolean{
mas01mj@732 410 return asyncQueue.length > 0;
mas01mj@732 411 }
mas01mj@732 412
mas01mj@732 413 protected function runTearDown():void {
mas01mj@732 414 if(currentState == TEAR_DOWN) {
mas01mj@732 415 return;
mas01mj@732 416 }
mas01mj@732 417 currentState = TEAR_DOWN;
mas01mj@732 418 if(isComplete) {
mas01mj@732 419 return;
mas01mj@732 420 }
mas01mj@732 421 if(!runSingle) {
mas01mj@732 422 getResult().endTestMethod(this, currentMethod);
mas01mj@732 423 tearDown();
mas01mj@732 424 layoutManager.resetAll();
mas01mj@732 425 }
mas01mj@732 426 setTimeout(runBare, 5);
mas01mj@732 427 }
mas01mj@732 428
mas01mj@732 429 /**
mas01mj@732 430 * Helper method for testing <code>DisplayObject</code>s.
mas01mj@732 431 *
mas01mj@732 432 * This method allows you to more easily add and manage <code>DisplayObject</code>
mas01mj@732 433 * instances in your <code>TestCase</code>.
mas01mj@732 434 *
mas01mj@732 435 * If you are using the regular <code>TestRunner</code>, you cannot add Flex classes.
mas01mj@732 436 *
mas01mj@732 437 * If you are using a <code>FlexRunner</code> base class, you can add either
mas01mj@732 438 * regular <code>DisplayObject</code>s or <code>IUIComponent</code>s.
mas01mj@732 439 *
mas01mj@732 440 * Usually, this method is called within <code>setUp</code>, and <code>removeChild</code>
mas01mj@732 441 * is called from within <code>tearDown</code>. Using these methods, ensures that added
mas01mj@732 442 * children will be subsequently removed, even when tests fail.
mas01mj@732 443 *
mas01mj@732 444 * Here is an example of the <code>addChild</code> method:
mas01mj@732 445 * <listing>
mas01mj@732 446 * private var instance:MyComponent;
mas01mj@732 447 *
mas01mj@732 448 * override protected function setUp():void {
mas01mj@732 449 * super.setUp();
mas01mj@732 450 * instance = new MyComponent();
mas01mj@732 451 * instance.addEventListener(Event.COMPLETE, addAsync());
mas01mj@732 452 * addChild(instance);
mas01mj@732 453 * }
mas01mj@732 454 *
mas01mj@732 455 * override protected function tearDown():void {
mas01mj@732 456 * super.tearDown();
mas01mj@732 457 * removeChild(instance);
mas01mj@732 458 * }
mas01mj@732 459 *
mas01mj@732 460 * public function testParam():void {
mas01mj@732 461 * assertEquals(34, instance.value);
mas01mj@732 462 * }
mas01mj@732 463 * </listing>
mas01mj@732 464 **/
mas01mj@732 465 protected function addChild(child:DisplayObject):DisplayObject {
mas01mj@732 466 return getContext().addChild(child);
mas01mj@732 467 }
mas01mj@732 468
mas01mj@732 469 /**
mas01mj@732 470 * Helper method for removing added <code>DisplayObject</code>s.
mas01mj@732 471 *
mas01mj@732 472 * <b>Update:</b> This method should no longer fail if the provided <code>DisplayObject</code>
mas01mj@732 473 * has already been removed.
mas01mj@732 474 **/
mas01mj@732 475 protected function removeChild(child:DisplayObject):DisplayObject {
mas01mj@732 476 if(child == null) {
mas01mj@732 477 return null;
mas01mj@732 478 }
mas01mj@732 479 try {
mas01mj@732 480 return getContext().removeChild(child);
mas01mj@732 481 }
mas01mj@732 482 catch(e:Error) {
mas01mj@732 483 }
mas01mj@732 484 return null;
mas01mj@732 485 }
mas01mj@732 486 }
mas01mj@732 487 }