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