What follows is version 1.0 of what I’m currently calling “ObjectManager”. Although the name clashes with one of Rational Functional Tester’s (RFT) classes, it’ll do for now. If you can think of something better, drop me a line, or leave a comment. Another thing before we start, please note that the ObjectManager code is licensed under the BSD License so you don’t *need* to let me know of the changes you make to the code, but I really would appreciate it and due credit will be given as I add your changes to the code.
What ObjectManager does is allow the following in your code:
ObjectManager om = new ObjectManager();
om.runThis("myAppLogonTxtUserName", "setText", "bob");
om.runThis("myAppLogonTxtPassword", "setText", "s33cr1t");
om.runThis("myAppLogonBtnLogin", "click");
om.runThis("myAppWelcomeWndMain", "waitForExistence");
String sCurrentTimeAsDisplayedByAUT;
sCurrentTimeAsDisplayedByAUT = om.runThis(“myAppWelcomeLblCurrentTime”, “getText”).toString();
Basically, you can pass an object name as a string, and a method name as a string. Arguments can be passed as any type of object (String, Integer, etc), as an array of objects (the array should be predefined), or don’t need to be passed at all.
What’s the advantage of this? Well, not much if you are only doing record/playback style test automation. If you are building a framework of some description, you may find it useful. It is the most dynamic way of dealing with mapped test objects in RFT that I know of.
How does it work? It makes *heavy* use of java’s Reflections functionality. If you want more detail, have a look at the code. There’s more comments than code…
Is it finished? Well, it does what it says on the tin (…there is no tin…), but it could do with better handling of exceptions. I’ll be working on it and hopefully I’ll update it over the next few days. Any input is welcome!
How to use it?
- Create an “RFT script” class in your project called “ObjectManager”
- Copy the following code into the new file
- Fix up the line that defines the package location
- Fix up the line that points to the helper file
- Map all the objects you want to use (this may be a massive effort, it has been for me) in either the private test object map (discouraged), or a public/standalone test object map that is linked to the ObjectManager class
- Figure out how ObjectManager is going to fit into your framework
- Make the appropriate “
runThis” calls wherever test object interaction is required.
If you can’t figure out why ObjectManager is so cool (though I say it myself), don’t worry about it. There are some very obvious ways that it can be used and extended, I’d be interested in your results!
/*
* Copyright (c) 2006, Nathaniel Ritmeyer
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
* - 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.
* - Neither the name of natontesting.com nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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.
*/
/*
* www.natontesting.com
*/
//change the following line to point to the proper package location
package Scratch;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Hashtable;
//change the following line to point to the correct helper file
import resources.Scratch.ObjectManagerHelper;
import com.rational.test.ft.object.interfaces.GuiTestObject;
import com.rational.test.ft.object.interfaces.TestObject;
import com.rational.test.ft.object.map.SpyMappedTestObject;
/**
* This class is designed to allow dynamic use of an object map. The main feature is
* the runThis(ObjectName, Method, Arguments) method. It takes in the ObjectName, the
* Method as strings, and the Arguments as an array of objects. Using reflection, the
* method creates an instance of the mapped object, and runs the method against it,
* with the arguments provided.
*
* The mapped objects should be in this classes helper file, preferably by the use of
* a public Test Object Map.
*
* Other methods have been provided that will help when going through the initial
* object mapping trauma. The “countAllMappedTestObjects” method provides a count of
* all of the currently mapped objects in this class’ helper file. Useful it you’re a
* stats junkie like me. The “printAllMappedTestObjects” method will print a list of
* all the object names to the console. Useful if you’re going to export the list to
* a text file, database, spreadsheet, etc…
*/
public class ObjectManager extends ObjectManagerHelper
{
public void testMain(Object args[])
{
//countAllMappedTestObjects();
//printAllMappedTestObjects();
}
/**
* Saves hassle if the method doesn’t take any arguments
*
* @param sObjectName –the exact name of the RFT Mapped Object you want to use
* @param sMethod –the exact name of the method you want to run against the object
* @param oArg –the argument to the method to run against the object
* @return Object –this will be whatever the method “sMethod” returns
* @throws InvocationTargetException –the method you wanted to call has thrown an exception
* @throws Exception –this could be any of the exception types below – catch
* these, they’ll let you know what went wrong, if anything.
*/
public Object runThis(String sObjectName, String sMethod) throws Exception, InvocationTargetException
{
return runThis(sObjectName, sMethod, null);
}
/**
* Saves hassle if the method needs only one argument instead of
* an array of them.
*
* @param sObjectName –the exact name of the RFT Mapped Object you want to use
* @param sMethod –the exact name of the method you want to run against the object
* @param oArg –the argument to the method to run against the object
* @return Object –this will be whatever the method “sMethod” returns
* @throws InvocationTargetException –the method you wanted to call has thrown an exception
* @throws Exception –this could be any of the exception types below – catch
* these, they’ll let you know what went wrong, if anything.
*/
public Object runThis(String sObjectName, String sMethod, Object oArg) throws Exception, InvocationTargetException
{
Object myObjectArgs[] = new Object[1];
myObjectArgs[0] = oArg;
return runThis(sObjectName, sMethod, myObjectArgs);
}
/**
* Runs a method against an object using the arguments passed in.
*
* @param sObjectName –the exact name of the RFT Mapped Object you want to use
* @param sMethod –the exact name of the method you want to run against the object
* @param sArgs –the arguments to the method to run against the object
* @return Object –this will be whatever the method “sMethod” returns
* @throws InvocationTargetException –the method you wanted to call has thrown an exception
* @throws Exception –this could be any of the exception types below – catch
* these, they’ll let you know what went wrong, if anything.
*/
public Object runThis(String sObjectName, String sMethod, Object sArgs[]) throws Exception, InvocationTargetException
{
/////////////////////////////////////////////////////////////////////////////
//////////////////////// Initial Rational Stuff /////////////////////////
/////////////////////////////////////////////////////////////////////////////
//get a reference to the desired object in the object map
SpyMappedTestObject smto = getMappedTestObject(sObjectName);
/////////////////////////////////////////////////////////////////////////////
/////////////////////////// Class Discovery /////////////////////////////
/////////////////////////////////////////////////////////////////////////////
//get the class of the above object reference
String sRFTClassForAUTObject = smto.getTestObjectClassName();
//get the class of the object referenced by the ‘smto’ object
Class cRFTObjectClass;
try
{
cRFTObjectClass = Class.forName(“com.rational.test.ft.object.interfaces.” + sRFTClassForAUTObject);
}
catch(ClassNotFoundException cnfe)
{
throw(cnfe);
}
/////////////////////////////////////////////////////////////////////////////
/////////////////////////// Constructor Setup ///////////////////////////
/////////////////////////////////////////////////////////////////////////////
//set up an object to reference the constructor method of the cRFTObjectClass
Constructor cnRFTObjectClassConstructor;
//since it is possible to have more than one constructor, you need to specify
//which one you want. Constructors are differentiated based on the parameters
//passed to them.
//We need to set up a class array that matches what the RFTClass expects. In
//this instance, that is just the “smto” object, so the array we set up has
//length of 1 containing only the “SpyMappedTestObject” class type.
Class cnConstructorArgs[] = new Class[1];
cnConstructorArgs[0] = SpyMappedTestObject.class;
//now that we have that class array, we can “get” the constructor
try
{
cnRFTObjectClassConstructor = cRFTObjectClass.getConstructor(cnConstructorArgs);
}
catch(NoSuchMethodException nsme)
{
throw(nsme);
}
//We are getting close to being able to run the constructor… we just need to
//put the arguments to the constructor into an array… once we’ve done that,
//we’re done…
SpyMappedTestObject smtoArray[] = new SpyMappedTestObject[1];
smtoArray[0] = smto;
//Done. We can now run the constructor with the arguments we just created
Object oActualRFTObject;
try
{
oActualRFTObject = cnRFTObjectClassConstructor.newInstance(smtoArray);
}
catch(InvocationTargetException ite)
{
throw(ite);
}
catch(IllegalAccessException iae)
{
throw(iae);
}
catch(InstantiationException ie)
{
throw(ie);
}
/////////////////////////////////////////////////////////////////////////////
////////////////////// Method Setup and Execution ///////////////////////
/////////////////////////////////////////////////////////////////////////////
//We now need to query the class to see if it contains a method that matches
//the one we want to run. To do that, we need to set up a class array containing
//a list of classes in the same order as the ones we want the method to accept
Class cArgType[];
if(sArgs == null)
{
//if no args got passed in, create an empty array
cArgType = new Class[0];
}
else
{
cArgType = new Class[sArgs.length];
//we use this magic ‘for’ loop to create the class array.
for(int iLoopCounter = 0; iLoopCounter < sArgs.length; iLoopCounter++)
{
//To check the type of the object and create an object instance for method
cArgType[iLoopCounter] = getCorrectClassForName(sArgs[iLoopCounter].getClass().getName());
}
}
//Create the reference to the method we want to run
Method mMethodToRunAgainstActualObject;
//get the method, based on its name and the argument types
try
{
mMethodToRunAgainstActualObject = cRFTObjectClass.getMethod(sMethod, cArgType);
}
catch(NoSuchMethodException nsme)
{
/*
* You’ll end up here if the method you want to execute doesn’t exist.
* Need to do more here. Check www.natontesting.com for the latest version.
*/
throw(nsme);
}
//set up an object to hold the return from the method
Object oReturnFromMethod = null;
//run the method!
try
{
oReturnFromMethod = mMethodToRunAgainstActualObject.invoke(oActualRFTObject, sArgs);
}
catch(InvocationTargetException ite)
{
/*
* If we get here, it means that whatever method was run threw an exception. We
* need to capture this.
* Eg: ObjectNotFoundException will arrive here…
* Need to do more here. Check www.natontesting.com for the latest version.
*/
throw(ite);
}
catch(IllegalAccessException iae)
{
/*
* If you try to call a private method, it’ll fail and you’ll end
* up here, and you’ll look really stupid.
* Need to do more here. Check www.natontesting.com for the latest version.
*/
throw(iae);
}
catch(IllegalArgumentException iae)
{
/*
* The aguments you passed to the method were somehow wrong
* Need to do more here. Check www.natontesting.com for the latest version.
*/
throw(iae);
}
catch(Exception e)
{
throw(e);
}
//put away your toys after playing with them…
unregisterAll();
return oReturnFromMethod;
}
/**
* Prints a count of the test objects to the console
*/
public void countAllMappedTestObjects()
{
Enumeration e = getAllMappedTestObjects();
long lCount = 0;
while(e.hasMoreElements())
{
lCount++;
e.nextElement();
}
System.out.println(“Number of Objects: ” + lCount);
}
/**
* Prints all the mapped test objects to the console
*/
public void printAllMappedTestObjects()
{
Enumeration e = getAllMappedTestObjects();
while(e.hasMoreElements())
{
System.out.println(e.nextElement());
}
}
/**
* Returns an enumeration containing the names of all of the mapped objects
* @return Enumeration
*/
private Enumeration getAllMappedTestObjects()
{
return this.getScriptDefinition().getTestObjectNames();
}
/**
* Returns a class object based on the sClassName parameter, corrected
* for primitive types
* @param sClassName –the name of the candidate class
* @return a class object
* @throws Exception
*/
private Class getCorrectClassForName(String sClassName) throws Exception
{
//We’ll hold the name of the correct class in here
String sClassToCreate = “”;
/*
* The following if/else stuff will check to see if the candidate
* class is one that represents a primitive type. If it is, set the
* class to return to be the corresponding primitive. Otherwise
* set the class to return as the original type
*/
if(sClassName.compareTo(“java.lang.Boolean”) == 0)
{
return boolean.class;
}
else if(sClassName.compareTo(“java.lang.Character”) == 0)
{
return char.class;
}
else if(sClassName.compareTo(“java.lang.Byte”) == 0)
{
return byte.class;
}
else if(sClassName.compareTo(“java.lang.Short”) == 0)
{
return short.class;
}
else if(sClassName.compareTo(“java.lang.Integer”) == 0)
{
return int.class;
}
else if(sClassName.compareTo(“java.lang.Long”) == 0)
{
return long.class;
}
else if(sClassName.compareTo(“java.lang.Float”) == 0)
{
return float.class;
}
else if(sClassName.compareTo(“java.lang.Double”) == 0)
{
return double.class;
}
else
{
return Class.forName(sClassName); //original class type
}
}
}