Skip to content

Instantly share code, notes, and snippets.

@nodename
Created March 16, 2010 04:55
Show Gist options
  • Save nodename/333660 to your computer and use it in GitHub Desktop.
Save nodename/333660 to your computer and use it in GitHub Desktop.
modified version of David Wolever's ParameterizedRunner
// (c) 2010 David Wolever <david@wolever.net> and Verso Furniture Inc.
// See the bottom of this file for copyright details.
package flexunit4Param.testrunners
{
import flash.utils.getQualifiedClassName;
import flex.lang.reflect.Method;
import flex.lang.reflect.metadata.MetaDataAnnotation;
import flex.lang.reflect.metadata.MetaDataArgument;
import org.flexunit.internals.runners.model.EachTestNotifier;
import org.flexunit.internals.runners.statements.Fail;
import org.flexunit.internals.runners.statements.IAsyncStatement;
import org.flexunit.internals.runners.statements.StatementSequencer;
import org.flexunit.runner.*;
import org.flexunit.runner.notification.IRunNotifier;
import org.flexunit.runners.BlockFlexUnit4ClassRunner;
import org.flexunit.runners.ParentRunner;
import org.flexunit.runners.model.FrameworkMethod;
import org.flexunit.token.AsyncTestToken;
import org.flexunit.token.ChildResult;
import org.flexunit.utils.ClassNameUtil;
/**
* Extends the BlockFlexUnit4ClassRunner, allowing children to specify
* arguments which will be passed to the test methods.
* Children returned by 'computeTestMethods' are expected to be of class ParameterizedFrameworkMethod
* (see bottom of this file)
*/
/**
* Adds the optional 'params' property to [Test] metadata.
* For example:
*
* [RunWith("utils.testrunners.ParameterizedRunner")]
* class AdditionTests {
* // Force the MXMLC to link in the ParameterizedRunner class
* ParameterizedRunner;
*
* // Note: params must be a public static function
* public static function numbersToTest():Array
* {
* return
* [
* [1, 2, 3],
* [4, 5, 9],
* [-1, 1, 0]
* ];
* }
*
* [Test(params="numbersToTest")]
* public function testAddition(a:int, b:int, expected:int):void {
* assertEqual(a+b, expected);
* }
* }
*
* Will cause three tests to be executed.
*/
public class ParameterizedRunner extends BlockFlexUnit4ClassRunner
{
public function ParameterizedRunner(klass:Class)
{
_computedTestMethods = null;
super(klass);
}
override protected function runChild(child:*, notifier:IRunNotifier, childRunnerToken:AsyncTestToken):void
{
var method:FrameworkMethod = FrameworkMethod(child.method);
var eachNotifier:EachTestNotifier;
eachNotifier = new EachTestNotifier(notifier, describeChild(child));
if (method.hasMetaData("Ignore"))
{
eachNotifier.fireTestIgnored();
childRunnerToken.sendResult();
return;
}
var token:AsyncTestToken;
token = new AsyncTestToken(ClassNameUtil.getLoggerFriendlyClassName(this));
token.parentToken = childRunnerToken;
token.addNotificationMethod(handleBlockComplete);
token[ParentRunner.EACH_NOTIFIER] = eachNotifier;
var error:Error;
eachNotifier.fireTestStarted();
try
{
var block:IAsyncStatement = parameterizedMethodBlock(method, child.args);
block.evaluate(token);
}
catch (e:Error)
{
error = e;
eachNotifier.addFailure(e);
}
if (error)
{
eachNotifier.fireTestFinished();
childRunnerToken.sendResult();
}
}
protected function parameterizedMethodBlock( method:FrameworkMethod, args:Array ):IAsyncStatement {
var c:Class;
var test:Object;
//might need to be reflective at some point
try {
test = createTest();
} catch ( e:Error ) {
trace( e.getStackTrace() );
return new Fail(e);
}
var sequencer:StatementSequencer = new StatementSequencer();
sequencer.addStep( withBefores( method, test) );
sequencer.addStep( withParameterizedDecoration( method, test, args ) );
sequencer.addStep( withAfters( method, test ) );
return sequencer;
}
protected function withParameterizedDecoration( method:FrameworkMethod, test:Object, args:Array ):IAsyncStatement {
var statement:IAsyncStatement = methodWithArgsInvoker( method, test, args );
statement = withPotentialAsync( method, test, statement );
statement = withPotentialTimeout( method, test, statement );
statement = possiblyExpectingExceptions( method, test, statement );
statement = withStackManagement( method, test, statement );
return statement;
}
protected function methodWithArgsInvoker(method:FrameworkMethod, test:Object, args:Array):IAsyncStatement
{
return new InvokeMethodWithArgs(method, test, args);
}
protected function handleBlockComplete(result:ChildResult):void
{
var error:Error = result.error;
var token:AsyncTestToken = result.token;
var eachNotifier:EachTestNotifier = result.token[EACH_NOTIFIER];
if (error)
{
eachNotifier.addFailure(error);
}
eachNotifier.fireTestFinished();
token.parentToken.sendResult();
}
protected var _computedTestMethods:Array;
override protected function computeTestMethods():Array
{
if (_computedTestMethods != null)
{
return _computedTestMethods;
}
var testMethods:Array = super.computeTestMethods();
_computedTestMethods = [];
for each (var frameworkMethod:FrameworkMethod in testMethods)
{
var metadata:Array = frameworkMethod.metadata;
outerLoop: for each (var annotation:MetaDataAnnotation in metadata)
{
if (annotation.name == "Test")
{
for each (var arg:MetaDataArgument in annotation.arguments)
{
if (arg.key == "params")
{
replaceMethodWithParameterizedMethods(frameworkMethod, arg.value);
break outerLoop;
}
}
replaceMethodWithNullArgsMethod(frameworkMethod);
break;
}
}
}
return _computedTestMethods;
function replaceMethodWithNullArgsMethod(method:FrameworkMethod):void
{
_computedTestMethods.push(new ParameterizedFrameworkMethod(method, []));
}
function replaceMethodWithParameterizedMethods(method:FrameworkMethod, paramName:String):void
{
var found:Boolean = false;
var testClassMethods:Array = testClass.klassInfo.methods;
for each (var testClassMethod:Method in testClassMethods)
{
if (testClassMethod.name == paramName)
{
if (testClassMethod.isStatic == false)
{
throw new Error(paramName + " is not a STATIC method of " + testClass.asClass);
}
found = true;
break;
}
}
if (!found)
{
throw new Error(paramName + " is not a method of " + testClass.asClass);
}
try
{
var allParams:Array = testClass.asClass[paramName]();
}
catch (e:Error)
{
throw new InitializationError("Error executing params method " + testClass.name + "." + paramName + "()");
}
for each (var thisTestParams:* in allParams)
{
if (!(thisTestParams is Array))
{
thisTestParams = [thisTestParams];
}
_computedTestMethods.push(new ParameterizedFrameworkMethod(method, thisTestParams));
}
}
}
protected var ARG_LENGTH:int = 25;
protected function describeArgs(args:Array):String
{
return args.map(mapping).join(", ");
function mapping(arg:*, ..._):String
{
var argStr:String = arg.toString();
argStr = argStr.replace(/^[ \t\n]*/, "");
return truncate(argStr, ARG_LENGTH);
}
}
override protected function describeChild(child:*):IDescription
{
var description:String;
description = getQualifiedClassName(testClass.asClass) + ".";
description += child.method.name + "(";
description += child.description ? child.description :
describeArgs(child.args);
description += ")";
return new Description(description, child.method.metadata);
}
/**
* Adds to <code>errors</code> for each method annotated with <code>Test</code> that
* is not a public, void instance method.
*/
override protected function validateTestMethods( errors:Array ):void {
validatePublicVoidMethods( "Test", false, errors);
}
/**
* Adds to <code>errors</code> if any method in this class is annotated with
* <code>metaDataTag</code>, but:
*
* <ul>
* <li>is not public, or</li>
* <li>returns something other than void, or</li>
* <li>is static (given <code>isStatic</code> is <code>false</code>), or</li>
* <li>is not static (given <code>isStatic</code> is <code>true</code>).</li>
* </ul>
*
* @param metaDataTag The metadata tag used to retrieve the methods.
* @param isStatic a Boolean value indicating whether the methods should be static.
* @param errors An <code>Array</code> of issues encountered when attempting to setup
* the runner for the test class.
*/
protected function validatePublicVoidMethods( metaDataTag:String, isStatic:Boolean, errors:Array ):void {
var methods:Array = testClass.getMetaDataMethods( metaDataTag );
var eachTestMethod:FrameworkMethod;
for ( var i:int=0; i<methods.length; i++ ) {
eachTestMethod = methods[ i ] as FrameworkMethod;
eachTestMethod.validatePublicVoid( isStatic, errors );
}
}
protected static function truncate(string:String, maxLength:int, removeEnd:Boolean=true):String
{
if (string.length <= maxLength)
{
return string;
}
if (removeEnd)
{
return string.substr(0, maxLength - 1) + "…";
}
else
{
return "…" + string.substring(string.length - maxLength + 1, string.length);
}
}
}
}
import org.flexunit.runners.model.FrameworkMethod;
/* method:FrameworkMethod = the method to run,
* args:Array = arguments to pass to the method,
* description:String = an optional description of the test
*/
class ParameterizedFrameworkMethod
{
private var _method:FrameworkMethod;
public function get method():FrameworkMethod { return _method; }
private var _args:Array;
public function get args():Array { return _args; }
public var description:String;
public function ParameterizedFrameworkMethod(method:FrameworkMethod, args:Array)
{
super();
_method = method;
_args = args;
}
}
/*
tl;dr: this code is licensed under simplified BSD.
Copyright 2010 David Wolever <david@wolever.net> and Verso Furniture Inc. All
rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of <copyright holder>.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment