MyGroup JUnitScript
 
   

BeanShell

printer
print-friendly
PDF

Extensions Available to BeanShell

All normal commands are available. However, the following test centric commands and beans have been added.

bsh.args

An array of command line arguments is bound to a bean named args. If no command line arguments are used then the array will have length 0. The elements of the array are all strings.

Assertions

A command is made available for every public method of the class com.hjl.junit.Check . Thus, in a BeanShell script, one may write:

assertTrue( 1 == 1 );

For a complete list of methods see the javadoc for the Check class.

assertThrows

An additional assertion is made available which is enabled by the scripting abstractions of BeanShell. This assertion allows the specification that a fragment of BeanShell script (as a string) throws and exception, possibly of a set of particular types.

The syntax for this method follows one of the these prototypes:

  • void assertThrows( code )
  • void assertThrows( code, exceptionTypes )
  • void assertThrows( code, exceptionTypes, message )

The first and second forms are equivalent to passing null in the missing arguments to the third form.

The code parameter is any object that can be converted to a string. That string will be interpreted as a fragment of BeanShell script by calling eval on it. If that does not throw an exception, then the message parameter is used to initialize a junit.framework.AssertionFailedException and that is thrown to indicate failure.

If evaluating the code does throw an exception, then that exception if compared to the exceptionTypes parameter. If the exception does not match, then the message parameter is used to initialize a junit.framework.AssertionFailedException and that is thrown to indicate failure.

The matching of the exception to the exceptionTypes parameter is performed differently according to the type of the parameter.

Type of exceptionTypes Matching
null Any exception matches
String The class named by the string is retrieved and if the exception is an instance of that class then it matches.
Class or BeanShell Class Identifier If the exception is an instance of that class then it matches.
Array Go through the array, for each element use the two matching methodologies above (for String, Class and Class Identifier) and if the exception matches for one element of the array then it matches. Otherwise it does not match and a failure exception will be thrown.

reflector

There are some small issues that I run into when using BeanShell for whitebox testing. I beleive BeanShell does not propagate cast information on nulls. This often causes a problem with finding and executing methods, particularly private ones.

To solve this problem there is a class, com.hjl.junit.Reflector . This permits prototype strings to be used to identify methods, fields and constructors. A bean deriving from this class (actually a subclass that uses the imports of BeanShell to simplify the strings further) is defined under the name reflector.

Example use is:

// Get the protected getClass method of the Reflector
import com.hjl.junit.Reflector;
m = reflector.getMethod( "Reflector.getClass( String )" );
m.setAccessible( true );
m.invoke( reflector, new Object[] { "int[]" } );

However, it may be easier to use the Binder class below.

binder

The reflection mechanism from the bean above can be a little clunky. Personally, I hate having to create the arrays to use reflection. A solution to this problem is presented. This involves binder objects.

binder objects allow you to add methods, constructors and fields to them. These additions are then callable with a syntax very similar to the normal Java syntax.

The members to be added can be specified either by passing in a java.lang.reflect.Constructor, java.lang.reflect.Field, java.lang.reflect.Method as appropriate, or by using as similar string syntax as used by the reflector bean. If a string is used, then setAccessible( true ) will be called on the member, otherwise it will not.

binder objects also take a this object which will be used in invoking non-static members.

First we create a binder object:

// A binder with no 'this' object.
b1 = binder();
// A binder with a 'this' object already provided.
b2 = binder( myObject );

If we need to set or change the 'this' object associated with the binder, we can use the setThis method. Note that if only constructors or static members are to be used, then this is not neccessary.

b1.setThis( myObject );

We can add to constructors to the binder like this:

b1.addConstructor( "com.foo.MyClass()" );
// We could have passed an actual constructor instead
java.lang.reflect.Constructor constr = ...;
b1.addConstructor( constr );
// Or with the import
import com.foo.MyClass;
b1.addConstructor( "MyClass()" );

This then adds a method to the binder with the following signature:

com.foo.MyClass b1.MyClass()

Thus, calling that method on b1 is equivalent to calling the constructor for MyClass. The name is inferred from the constructor. However, a binder can only have one member assigned to each name, hence each addXXX method can also take a name to assign. It is possible to set a constructor, use it, and then set another constructor to the same name.

import com.foo.MyClass;
b = binder();

b.addConstructor( "MyClass()", "MyClass1" );
b.addConstructor( "MyClass( int )", "MyClass2" );
b.addConstructor( "MyClass( String )", "MyClass3" );
b.addConstructor( "MyClass( MyClass )", "MyClass4" );

mc1 = b.MyClass1();
mc2 = b.MyClass2( 10 );
mc3 = b.MyClass3( "hello" );
mc3 = b.MyClass4( mc1 );

Methods are handled in a similar way, but one must remember to set the 'this' object where appropriate.

import com.foo.MyClass;
b = binder();

b.addMethod( "MyClass.aStaticMethod( int, String )" );
b.aStaticMethod( 10, "hello" );

b.addMethod( "MyClass.aMethod( int, String )" );

b.setThis( new MyClass( ));
b.aMethod( 10, "hello" );

Fields however have a slightly different syntax. Fields are represented by a pair of methods, not dissimilar to normal getters and setters. However, the names do not begin with get or set. So, calling:

import com.foo.MyClass;
b = binder();

b.addField( "MyClass.aField" );

Will add two methods to b (assuming the field has type T):

T b.aField(); // getter
void b.aField( T t ); // setter

Giving usage examples like this:

import com.foo.MyClass;
b = binder();

b.addField( "MyClass.aStaticField" ); // Assume int for the example
b.addField( "MyClass.aField" ); // Assume int for the example

b.aStaticField( 10 );
assertEquals( b.aStaticField(), 10 );

b.setThis( new MyClass( ));
b.aField( 10 );
assertEquals( b.aField(), 10 );
Warning
Name mangling is used to store book keeping information in the binder object. The names of these data all begin with _$_$_$. So, if you intend to use such names, you might have to use conventional reflection.

One final problem that can be raised is when the names we wish to add to the binder conflict with the initial methods, setThis, addConstructor, addField or addMethod. This problem may be circumvented most simply by using the additional nameargument of the binder object. However another solution is to use the predefined Binder bean which is of class, com.hjl.junitscript.languages.beanshell.Binder .

The methods on this class allow direct access to the binder. Indeed the implementation of the addXXX methods in the binder delegate directly to this bean.