Mercurial > hg > audiodb
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 } |