Showing posts with label classes. Show all posts
Showing posts with label classes. Show all posts

December 13, 2011

QTP and Method Chaining disclosed

With method chaining, you can chain object methods to an object in order to change it. It is the default paradigm of JQuery. After using it a while with JQuery, I figured out it is an extremely usefull method to use with QTP.

If you use QTP long enough, you start to create your own function. But because VBScript doesn't allow optional parameters in homebrew functions, existing functions become clumsy with all types of parameters you don't use most of the time.
I remember using functions like findTextInTable(oTbl, txtToFind, startRow, endRow, startColumn, endColumn, byRef foundRow, byRef foundColumn) where each parameter could also be an array or contain signal characters. This results in a function call like this:
findTextInTable(myTable, array("account xyz", "!customer \d+"), 4, "", "", 5, foundRow, "")
So far for self-explanatory code.

When you use object chaining, you use only the parameters you need AND your code will become self explanatory. The code above will become:
xTable.addFindText("account xyz").addRegexFindText("customer \d+").startRow(4).endColumn(5).getFindRow(foundRow)

Of course this need some programming skills to build this correctly into a class, so let's start with an easy example: A string wrapper. When we create the class for the stringwrapper, we use functions for the methods. Functions can return primitives or objects, and with each object call, we return the object itself. You can also use "Get Property", but that will restrict you in the amount of parameters you can pass with your method to be exactly the same as the Let/Set Property.

Class cls_xString

    Private value_

    Public Function SetValue(v)
        value_ = v
        Set SetValue = me
    End Function

    Public Default Function getValue()
        getValue = value_
    End Function

    Public Function ToUpper()
        value_ = uCase(value_)
        Set ToUpper = me
    End Function

    Public Function Append(v)
         value_ = value_ & v
        Set Append = me
    End Function

    Public Function Prepend(v)
        value_ = v & value_
        Set Prepend = me
    End Function

    Public Function Surround(v)
        value_ = v & value_ & v
        Set Surround = me
    End Function

End Class

We use another trick with the getValue; it is the default return value if the string class is called without a Set. To make the class usable globally, we need an initiator function that returns an object of class xString:

Public Function [new xString](initialValue)
    Set [new xString] = new cls_xString
    call [new xString].SetValue(initialValue)
End Function

And now we can create some testcode:
Dim myString
Set myString = [new xString]("W00t, Method Chaining is really Working!")
msgbox myString.ToUpper.Prepend("(").Append(")").Surround("'")

This will result in an editbox with text '(W00T, METHOD CHAINING IS REALLY WORKING!)'
The method are parsed from left to right; first the string is transformed to uppercase, then prepended by (, appended by ) and finally surrounded by '.

Other usage:
- Narrowing down a collection and get a single object
Set linkCollection = [new linkCollection](oPage)    ' Get all links from a page
Set myLink = linkCollection.withRegexText(".*Help.*").Index(3)

Or do directly an action on that object:
linkCollection.withRegexText(".*Help.*").Index(3).Click

- Narrowing down collections and do actions on them:
Set editCollection = [new editboxCollection](oPage)    ' Get all editboxes from a page
' Set all editboxes that are visible, enabled and empty with a value
editCollection.withProperty("visible:=true").withProperty("enabled:=true").withValue("").setValue("Foo Bar!")

- Do a custom action on an object
Private Function DisplayToString(object)
    MsgBox object.ToString()
End Function
Set editCollection = [new editboxCollection](oPage)    ' Get all editboxes from a page
' Show the toString value of all editboxes that are not visible on a page
editCollection.withProperty("visible:=false").each(getRef("DisplayToString"))

Method chaining is very versatile, with the trap of being used to much. Actually it does pretty much the same using the 'with' keyword with objects. It can make your code more clear, or just more obfuscated. Use it wisely.

November 18, 2010

QTP and how to create an autodestruct class

Somewhere in our application under test, we have a webpage that only continues loading if the mouse is slightly moved. The page is loading while QTP is syncing the page. During the syncing, I cannot send mouse move commands from QTP to the application.

I don’t know why the application has this feature and “they” don’t want to fix it, so… work around time.


I created a low profile mousemove application in C that only does one thing: After 5 seconds it moves the mouse cursor one pixel to the right. And after 5 seconds it moves the cursor one pixel to the left. Repeat.


Actually it is an cut down version of out nolock.exe application, this application sniffs the mouse cursor movements. If it detects inactivity for 10 minutes, it moves the mouse cursor slightly in the same manner as discussed above. This program has a shortcut in the Windows Startup folder and prevent the screen from locking when automated tests are running.


But back to the autodestruct class. We want to have a “single line of code” to implement this in any function where we expect the screen to hang. This class starts the mouse moving as soon as it is created and stops the mouse moving when it is destroyed.


Private Const MOUSE_MOVE_APPL_NAME = “mouseMover.exe”

Private Const BINARIES_FOLDER = “C:\QTP_Framework\tooling\bin\”


Class cls_AutoDestructMouseMover

Private Sub class_initialize()

‘ Put the cursor somewhere to prevent it is on the extreme right position

call extern.SetCursorPos(100, 150)

‘ Run the external mouse move application

systemutil.Run BINARIES_FOLDER & _

MOUSE_MOVE_APPL_NAME, "", BINARIES_FOLDER

End Sub


Private sub class_terminate()

‘ Kill the mouse move application

systemUtil.CloseProcessByName MOUSE_MOVE_APPL_NAME

End Sub

End Class


And make a public accessor for the class:


Public function [new AutoDestructMouseMover]

Set [new AutoDestructMouseMover] = new cls_AutoDestructMouseMover

End Function


Now, we can implement the class whenever it is needed:


Public Function Example

‘ Create the object, make it move!

Dim autoDestructMouseMover : Set autoDestructMouseMover = [new AutoDestructMouseMover]

Call TimeConsumingFunction


‘ Destroy the object, let it stop!

Set autoDestructMouseMover = Nothing

End Function


But wait, we called it an “autodestruct” class, what about the autodestruct? Well, The last line of code is not necessary. When the Example function ends, it automatically destroys all variables and objectpointers with local scope. We can simply reduce the implementation of the autodestructor to one line of code (I know, I am cheating with the semicolon):


Public Function Example

‘ Create the object, make it move!

Dim autoDestructMouseMover : Set autoDestructMouseMover = [new AutoDestructMouseMover]

Call TimeConsumingFunction

End Function


And the autoDestructMouseMover object is automatically destroyed with the function clean up.


Nice. But are there more appliances for an autodestruct object? Oh, yes.

What do you think an autoDestructStackTracer?

Class cls_AutoDestructStackTracer

Private procedureName_

Public Sub Init(pName)

procedureName_ = pName

Print now & “ ADST Start “ & procedureName_

End Sub


Private sub class_terminate()

Print now & “ ADST Exit “ & procedureName_

End Sub

End Class


And make a public accessor for the class:


Public function [new AutoDestructStackTracer](pName)

Set [new AutoDestructStackTracer] = new cls_AutoDestructStackTracer

[new AutoDestructStackTracer].Init pName

End Function


And let’s test it:


Public Function ExampleParent

Dim autoDestructStackTracer : Set autoDestructStackTracer = [new AutoDestructStackTracer](“ExampleParent”)

Print “I’m with ExampleParent now!”

Call ExampleChild

End Function


Public Function ExampleChild

Dim autoDestructStackTracer : Set autoDestructStackTracer = [new AutoDestructStackTracer](“ExampleChild”)

Print “I’m with ExampleChild now!”

End Function


Call ExampleParent

Output:

18-11-2010 9:06:06 ADST Start ExampleParent

I’m with ExampleParent now!

18-11-2010 9:06:06 ADST Start ExampleChild

I’m with ExampleChild now!

18-11-2010 9:06:06 ADST Exit ExampleChild

18-11-2010 9:06:06 ADST Exit ExampleParent


And consider uses of an [new AutoDestructFunctionTimer](pName) to measure the performance of your different functions. Or a [new ScreenShotOnFunctionExit] (and more generalized: [new ExecuteOnFunctionExit](“call PerformScreenshot”)) when you have lots of functions with multiple exit points.

April 26, 2009

How to use classes in QTP revisited

Classes in QTP are a bit tricky if you want to use them for the first time. You have to know and understand the principle that a class got local scope for the QTP Function Library (.qfl file) it is in, and that if you want to use it in another library, you’ll have to create a constructor (I have shown this before, but I’ll do it again):


' Constructor:
Public Function [new CustomClass]()
     Set [new CustomClass] = new cls_CustomClass
End Function

' Class definition:
Class cls_CustomClass
     Private Sub Class_Initialize()
         Print "Custom Class: I am created!"
     End Sub
End Class

' using the object in another library:
Dim myClass
Set myClass = [new CustomClass]

This seems like a drawback, but it creates a great possibility. Normally, it is not possible to pass initialisation parameters into a new class, because Class_Initialize does not accept parameters. But using the constructor function, we can!

Option Explicit

' Make a private variable that we can pass into the class
Private PARAMETER_ARRAY

' Create a function that returns the requested class. Accepts
' a parameter array. Note: Do not use paramArray, it is reserved!
Public Function [new customClass](ByVal parameterArray)

     PARAMETER_ARRAY = parameterArray

     ' Return a clsCustomClass object
     Set [new CustomClass] = new cls_CustomClass

End Function

' Create the custom class. Classes are always declared Private in QTP
Class cls_CustomClass

     ' The sub Class_Initialize is used to initialize the object with the
     ' parameters passed into the constructor
     Private Sub Class_Initialize()
         ' Do something with the parameters
         Print join(PARAMETER_ARRAY, vbNewLine)
     End Sub

End Class

' Test code
Dim myClass
Set myClass = [new customClass](array("first parameter", "second parameter", "etc."))

Notice what we just did: We created a Private variable, that can only be used by CustomClass classes. So with the use of Private variables with local scope to the library where you put the class in, you create a static variable for the class!
A static variable in this context is a variable that keeps its value and can be used over multiple objects of the same class:

' Declaring the static variables
Private INSTANCE_COUNTER : INSTANCE_COUNTER = 0

' Constructor:
Public Function [new CustomClass]()
     Set [new CustomClass] = new cls_CustomClass
End Function

' Class definition:
Class cls_CustomClass
     Private Sub Class_Initialize()
         Print "I am created and I have " & INSTANCE_COUNTER & " sister(s)."
         INSTANCE_COUNTER = INSTANCE_COUNTER + 1
     End Sub

     Private Sub Class_Terminate()
         INSTANCE_COUNTER = INSTANCE_COUNTER – 1
     End Sub
End Class

' using the object in another library:
Dim a, b, c
Set a = [new CustomClass]
Set b = [new CustomClass]
Set c = [new CustomClass]

Results in:
I am created and I have 0 sister(s).
I am created and I have 1 sister(s).
I am created and I have 2 sister(s).

By using the static variable as a reference for the object itself, we can create a singleton object. A singleton is an object whereof only one instance at a time can exist. In object oriented languages, singletons are normally used for large objects, or objects that will go bad if multiple instances exists like deadlocks or instability.

Option explicit

Private SINGLETON : Set SINGLETON = Nothing

Public function [new Singleton]()
     If SINGLETON Is Nothing Then
         set SINGLETON = new cls_Singleton
     End If

     Set [new Singleton] = SINGLETON
End Function

Class cls_Singleton

     Private Sub Class_Initialize
         Print "I am unique!"
     End Sub

End Class

Dim st1, st2, st3
Set st1 = [new Singleton]
Set st2 = [new Singleton]
Set st3 = [new Singleton]

Results in only one:
I am unique!

There is a drawback: Once a singleton object is created this way, it is not possible to destroy it without the use of some code violating the object oriented principle.

June 18, 2008

Continued: Creating a Queue data type in QTP

DONG, Housekeeping message: If you have questions, additional information, an opinion or just something to share, you are very welcome to leave it in the comments. I like to get personal mail too, but as mentioned on coding horrors: on a blog, the comments are the best part. And since I just started, I still need some!

In addition to a question I received through email: The class to create a queue on the linked list I published Monday, and how to perform a count on this queue.

Public function queue()
  set queue = new clsQueue
End Function

Class clsQueue

  Private ll

  Private Sub Class_Initialize  ' Setup Initialize event.
    set ll = linkedList     ' Create a linked list instance
    End Sub

  Public function pop()
    If ll.count = 0 Then exit function  ' No items in the list
        pop = ll.getfirst()     ' Return the last item
    ll.deleteFirst        ' and delete it
  End Function

  Public sub push(element)    
    ll.add element        ' Add an item to the list
    End sub

  Public function peek()      
    peek = ll.getfirst()      ' Peek at the top item
  End Function

  Public property get count()
    'return the amount of nodes
    count = ll.count
  End Property
  
End Class

And this is the part where inheritance and polymorphism is missing. The class looks kind of the same as the stack class, but .getlast() is replaced by .getfirst(). I wish everything in life was this simple… The count property is added, so you can now count the items in the queue. If you want to add a count property to the stack, just do the same in the clsStack.

June 16, 2008

Abstract data types in QTP: The linked list

Normally the need for abstract datatypes in Test Automation is not very high. But you will see, just if you don’t have direct access to them, you’ll need them most. Today I had to store some data and it would be nice if I could pop and push it on some stacks. The basis for stacks (and queues) is a linked list. This was a nice Monday morning starter. Linked lists are simple, if you have the right toolset. Unfortunately, QTP and VBScript do not support pointers, so I had to be a little creative. First of all, I created a node class. Each node has a unique index number and a reference to the index numbers of its left and right neighbours. The data is conserved in a public available data element that can contain objects or variants.

' This is a setup for a linked list. This linked list is the basis for
' queues and stacks. With a little adaptation this can be transformed to
' a binary three or more complex abstract data types.

Option explicit

' Initializer for a new listnode. If you keep it private, you don't need this
' caller function
Private function listNode()
    Set listNode = new clsListNode
End Function

' class for a node. The nodeIndex functions as pointerreference
Class clsListNode

    Public data              'Data, can be a variable or an object (not an array)
    Public prevNode
    Public nextNode
    Private nodeIndex        'Index reference

    ' I use a property get to make a defaulter
    Public default property get index()    
        index = nodeIndex    
    End Property

    ' and a property let to set a new indexer
    Public property let index(inr)
        nodeIndex = inr
    End Property
End class


To keep track of the nodes, I created an array called collection containing node elements. Collection() is a list of references, but is never referred directly except when we add a new node. If a new node is added, the subscript of collection is incremented to create a new unique reference. Because elements can be added or deleted randomly, do not use collection() and its subscript as a continuous or a chronological list!

With add(), we can add an element to the end of the linked list. With getlast() and getfirst() the last and first data elements on the linked list are returned and with deletelast() and deletefirst() the last and first nodes are deleted.

Count() is a helper function, returning the amount of nodes in the list.

' Make the linked list caller
Public function linkedList()
    set linkedList = new clsLinkedList
End Function

' The linked list call
Class clsLinkedList

    private    collection()       ' a single dimensional array of listNodes
    Private lastItemIndex         ' index of the last item
    Private firstItemIndex        ' index of the first item
    Private indexNumber           ' counter for nodes in the list. Only increment, never decrement!
    Private nodeCount             ' amount of nodes. Ugly but fast.
  
    Private Sub Class_Initialize  ' Setup Initialize event.
        lastItemIndex = null   
        firstItemIndex = null
        indexNumber = 0
        nodeCount = 0
    End Sub

    Public sub add(data)

        ' add new listNode element to the array
        ReDim preserve collection(indexNumber)
        set collection(indexNumber) = listNode

        With collection(indexNumber)
            ' Make the collection compatible for objects as well as normal variables
            If isObject(data) Then
                set .data = data
            else
                .data = data
            End If

            .prevNode = lastItemIndex    ' The previous node of the new node is the current
                                         ' last node reference
            .nextNode = null             ' As it is the last item, there is no reference
                                         ' to the next item
            .index() = indexNumber       ' Set the index of the node to a unique number

            If not isnull(.prevNode) Then     ' If the newly created node has a left neighbour:
                collection(.prevNode).nextNode = indexNumber    ' the nextnode reference of the left
            else                                                ' neighbour is the index of the newly created
                firstItemIndex = .index       ' Else, this is the first node and the first node reference is the
            end if                            ' index of the newly created node
        end with

        lastItemIndex = indexNumber          ' As this is the last node, set the reference to this indexnumber
        indexNumber = indexNumber + 1        ' make a new indexnumber for a unique reference next time
        nodeCount = nodeCount + 1            ' and increment the nodecounter
    End sub

    Public property get getlast()
        If nodeCount = 0 Then exit property                ' No nodes? exit!

        If isobject(collection(lastItemIndex).data) then   ' Object or variant?
            Set getlast = collection(lastItemIndex).data   ' return object
        else
            getlast = collection(lastItemIndex).data       ' return variant
        end if   
    End Property

    Public property get getfirst()

        If nodeCount = 0 Then exit property

        If isobject(collection(firstItemIndex).data) then
            Set getfirst = collection(firstItemIndex).data
        else
            getfirst = collection(firstItemIndex).data
        end if
   
    End Property

    Public sub deletelast()

        If nodeCount = 0 Then exit sub    'Exit on no nodes

        Dim tempLastIndex
        tempLastIndex = lastItemIndex     'Make a temp for the last index number
       
        ' Check if there is a previous node
        If not isnull(collection(lastItemIndex).prevNode)  Then   
                ' Set the reference for the last item to the index of the left neighbour
            lastItemIndex = collection(collection(lastItemIndex).prevNode).index
                ' Set the reference for the next node of the left neighbour to null
            collection(lastItemIndex).nextNode = null
        End If

        ' destroy the node element
        Set collection(tempLastIndex) = nothing

        ' decrement the node counter
        nodeCount = nodeCount - 1
    End Sub

    Public sub deletefirst()

        If nodeCount = 0 Then exit sub
       
        Dim tempFirstIndex
        tempFirstIndex = firstItemIndex

        ' Check if there is a next node
        If not isnull(collection(firstItemIndex).nextNode)  Then
                ' Set the reference for the first item to the index of the right neighbour
            firstItemIndex = collection(collection(firstItemIndex).nextNode).index
                ' Set the reference for the previous node of the right neighbour to null
            collection(firstItemIndex).prevNode = null
        End If

        Set collection(tempFirstIndex) = nothing
        nodeCount = nodeCount - 1
    End Sub

    Public property get count()
        'return the amount of nodes
        count = nodeCount
    End Property
   
end class 


Some test statements to show how it works::


Dim ll, mc
Set ll = linkedList

    ll.add "first"
    ll.add "second"
    ll.add "third"
    ll.add "fourth"
    ll.add "fifth"
   
msgbox ll.getFirst()            ' >first
msgbox ll.getLast()             ' >fifth

' Delete the first item
ll.deletefirst
msgbox ll.getFirst()            ' >second

msgbox ll.count                 ' >4

' Delete the last two items
ll.deletelast
ll.deletelast
msgbox ll.getLast()             ' >third


The linked list class makes it very easy to create stacks and queues. Here is an example of how to create a stack:


Public function stack()
    set stack = new clsStack
End Function

Class clsStack

    Private ll
    Private Sub Class_Initialize    ' Setup Initialize event.
        set ll = linkedList         ' Create a linked list instance
    End Sub

    Public function pop()
        If ll.count = 0 Then exit function    ' No items in the list
        pop = ll.getlast()          ' Return the last item
        ll.deleteLast               ' and delete it
    End Function

    Public sub push(element)       
        ll.add element              ' Add an item to the list
    End sub

    Public function peek()           
        peek = ll.getlast()         ' Peek at the top item
    End Function

End Class

And some sample code for a little demonstration:

Dim myStack
set myStack = stack         ' Set as new stack

myStack.push "item1"        ' Add some items
myStack.push "item2"
myStack.push "item3"

msgbox myStack.pop()        ' >item3
msgbox myStack.peek()       ' >item2
msgbox myStack.pop()        ' >item2 

The thing that is missing for generic use is an insertbefore() and an insertafter() method on the linked list. But to implement this, you'll need a virtual reference table that maps the collection() array index to a chronologic ánd continuous index. Another way to do this is a looping mechanism where you can walk through the elements with a getnext() or getprevious() method.
For now, the stack and queue functionality is sufficient for my needs.

June 2, 2008

QTP and Tricks with Classes


As a hardcore WinRunner test automater, I still not like the "one line, one statement" approach of VBScript, but the one thing I like is working with classes. I'm still playing around with it a little bit and I learn a bit each day. The thing I did today isn't new I think, but for me it was.

A little about my situation. We are (ab)using QTP as a dataentry tool. We can only use the frontend, because the backend is protected. (so long for third party software). There will be a backend interface soon, but soon in the ICT is still counted in multiple months and a quicker solution had to be found. Maybe that is why QTP is chosen. On the contrary, I think the program manager felt for the 'Q'. Right now, the data is coming from multiple sources and will be directed to the frontend with QTP.

And this is where I discovered a neat application of classes for the use in dataentry. I created a class, let's say "customer" and I redirected the class into a function as a method of that class.
Time for an example:

First, my class 'customer':

class customer
    public name
    public address
    public phone
end class

and my function to enter the customer into the frontend:

public function enterCustomer(byRef myCus)    ' Use byRef for speed and to save memory

    call navigateToCustomerCreate()        'I like functions more then subs

    with Browser(zzz).Page(yyy)
        .WebEdit("Name").Set myCus.name
        .WebEdit("Address").Set myCus.address
        .WebEdit("Phone").Set myCus.phone
    end with

end function

now, I added a function into the customer class to enter the customer:

class customer
    public name
    public address
    public phone

    public function enterData()
        call enterCustomer(me)        'The 'me' is referring to it's own class object 'customer'
    end function
end class

This creates a method for my customer object to enter my data.

Because a customer can have multiple accounts, I created a class account and that one is added as a dynamic array in the customer class. And for data integrity I created a validate method that checks if all mandatory fields are filled and if all data is in the correct formatting (hurray for regular expressions).
But I can add as much functionality as I like in a ordered way, the only call I have to make to get it all working is using the enterData() method in my driver script for entering all my data into the frontend.

The main reason why I use this method, is that I only have to write an import function for all three types of import (plain text, xml and through an ODBC connection) and redirect the data into the class. At the end I call myCustomer.enterData() and it is done.

In the mean time, testing my functions is very easy, I just create an object of the correct class and test the function by calling enterCustomer(myCustomer). I don't have to bother validation rules because they are only executed when the enterData() method is called. And because the method validate() is a public method in the customer class, I do not have to enter the data when I want to test the validation of it.

It seems a lot of work for only adding a customer but in the real situation, the datamodel contains a lot more objects with a lot more fields. This way, it keeps my code clean, structured and also important: testable.

June 1, 2008

QTP and Four Ways of creating Classes


QTP isn't build for the use of classes. If it was, we had a separated file where we could store our classes, a viewer extension and a set of exotic functions with almost enough functionality to handle our custom classes.
But we haven't have any of them, so we have to fiddle around with methods Mercury (I refuse to call them HP until their service is to the level we are used to experience) thought we are not capable of to use: 'Coz we're testers - not programmers.

These are four methods to implement classes. I found the last three on the net, the source is just down each method.

1. Just put them in your action script
And enjoy the experience of non-reusable classes. Just the main benefit of a class: reusability is taken away from you, because with each new action script, you have to define your class again (if you want to use it of course). This will only suit you if you only have one action script.
This is the case on my current project, because we don't use Quality Center and we use the action script more as the main driver then as what it is meant to be.
I still rather don't want to use it in the way I just described, because with only ten classes in our data model it is still getting crowded in the action script.

2. Put them in a function script
You can put your class into a separate function script and just add it to your project. But QTP wouldn't be QTP if this worked on first glance. As long as you only put it in there as a class, it wouldn't work, because in some way the class is seen as a private on that function library. It will not be recognized in other parts of your script.

The work around:
First, make a class

class prototypeContact
    public name
    public address
    public phone
end class

and create a function (in the same library), setting an object as that class and returning that object:

public function Contact()
    set Contact = new prototypeContact
end function

Now you can use your class by calling the function with a newly created object:

set myContact = Contact()

The function Contact() will assign a new object to myContact of the class prototypeContact.

First found on The Software Inquisition.


3. Put them in an external script
Create your classes in a new script and save it as .vbs file. From your main script (or action script), do a call to this library with ExecuteFile().

This works with one major drawback, you'll have a hard time debugging. As soon an error is raised, it will mention the line of your main script were ExecuteFile() was called and that's it.

First found on a comment on an article of The Software Inguisition.

4. Load them dynamically
This is the most complex way, but also the one with the most potential. I will not go into the deepest detail, you can read that in the original article, but I will explain the principle:

1. Dynamically create a text string that contains the class definition.
2. Execute the text string with the command "Execute" (for the WinRunner and Javascript readers: It does the same as the eval() function).
3. Now you can set an object as the just created class.

Because you create the string dynamically, you can adapt the class on the fly. I wouldn't dare to talk about inheritance or polymorphism, but you can imagine the power of runtime named classes, variables, objects, functions and properties.

There is still the drawback of item 3: It gives you a hard time debugging it when something went wrong. And that is a very big drawback if you think about bugs that can enter dynamic written code.

First found in an article on the blog of Stefan Thelenius.

Final Word
Not all methods are working on each release of QTP. ExecuteFile() for example is available as of version 9.0. You'll have to find out what method suits you best.