+ All Categories
Home > Documents > Advanced QTP

Advanced QTP

Date post: 30-Aug-2014
Category:
Upload: shiva-reddy
View: 223 times
Download: 2 times
Share this document with a friend
Popular Tags:
49
Functional Pointer in VBScript Abstract Function pointers are used in C and C++ to enable callbacks and as a result, more generic and parsimonious coding. This article shows how to implement function pointers in VB Script using the Scripting.Dictionary object and the Command Wrapper design pattern. It is also shown how to implement “constructor” functions, callbacks land event handlers. Keywords: function pointer, callback, delegate, scripting dictionary, hash table, design pattern, command wrapper, event handler. Introduction In the following sections I shall explain several basic concepts that are essential to understand the rationale behind the suggested method. The list might be non-exhaustive and the reader is hereby encouraged to recur to complementary sources to fill-in any eventual knowledge gaps. Function Pointers A function pointer is a variable that stores the memory address of a block of code that is programmed to fulfill a specific function. Function pointers are useful to avoid complex switch case structures; instead, they support direct access in run-time to previously loaded functions or class methods. This enables the construction of callback functions. A callback is, in essence, executable code that is passed as argument to a function. This enables more generic coding, by having lower-level modules calling higher-level functions or subroutines. Function pointers are supported by programming languages like C and C++. A recent good introductory text on the subject with implementation examples is Lars Haendel (2005) . This article shows how to implement function pointers in VB script.
Transcript
Page 1: Advanced QTP

Functional Pointer in VBScriptAbstractFunction pointers are used in C and C++ to enable callbacks and as a result, more generic and parsimonious coding. This article shows how to implement function pointers in VB Script using the Scripting.Dictionary object and the Command Wrapper design pattern. It is also shown how to implement “constructor” functions, callbacks land event handlers.

 

Keywords: function pointer, callback, delegate, scripting dictionary, hash table, design pattern, command wrapper, event handler.IntroductionIn the following sections I shall explain several basic concepts that are essential to understand the rationale behind the suggested method. The list might be non-exhaustive and the reader is hereby encouraged to recur to complementary sources to fill-in any eventual knowledge gaps.

Function PointersA function pointer is a variable that stores the memory address of a block of code that is programmed to fulfill a specific function. Function pointers are useful to avoid complex switch case structures; instead, they support direct access in run-time to previously loaded functions or class methods. This enables the construction of callback functions. A callback is, in essence, executable code that is passed as argument to a function. This enables more generic coding, by having lower-level modules calling higher-level functions or subroutines.

Function pointers are supported by programming languages like C and C++. A recent good introductory text on the subject with implementation examples is Lars Haendel (2005).This article shows how to implement function pointers in VB script.

Design PatternsDesign patterns are well established generic design techniques whose aim is to make software design and the derived code more parsimonious, scalable, and reusable, as well as more easily maintainable. This article will delve only into the command wrapper design pattern, which is important for its purpose. For a more extensive explanation of

Page 2: Advanced QTP

design patterns, the reader is encouraged to read the literature in the field (e.g., Gamma, Helm, Johnson, & Vlissides, 1997).

The command-wrapper design pattern enables to load blocks of code dynamically to memory as well as other uses as will be reviewed below. The singleton design pattern is used to restrict instantiation of a class to one object. This is useful when there is no need for more than one object within a system (e.g., a reporter/logger object). For example, the factory design pattern usually implements the singleton design pattern to have a single interface from which to retrieve references to a specific class object or to a variety of class objects.

The Command Wrapper Design PatternThe command wrapper design pattern is a technique that implements a function as a class, usually with a single public method. During run-time, it is possible to instantiate the class and thus to get a reference (or pointer) to the object. This way, the code embodied in such methods can be dynamically loaded, a feature that can be of great value in systems that are poor in resources. This is especially true when the to-be called function is rarely used. After creating the object, it is possible to execute its method according to need. For example, let us assume a function that adds numbers (note: this example is, of course, trivial. It is done just to illustrate the point.)

1: Public Function Sum(ByVal arrNumbers) 2: Dim ix 3: 4: 'Check if an array was passed. If so, there's nothing to do – exit function 5: If (Not IsArray(arrNumbers)) Then 6: 'Add your error handling code here 7: Exit Function 8: End If 9: 10: Sum = 0 11: For ix = LBound(arrNumbers) To UBound(arrNumbers) 12: If (IsNumeric(arrNumbers(ix))) Then 13: Sum = Sum + arrNumbers(ix) 14: Else 15: 'Add your error handling code here 16: End If 17: Next 18: End Function 19: 20: 'Test the function and display result returned by the Sum function 21: MsgBox Sum(Array(23, 56, 78, 95, 114)), vbOKOnly, "Result from simple function" 22: 23:

Page 3: Advanced QTP

24: 'Now, let us convert this function into an object using the Command Wrapper design pattern: 25: Class Sum 26: Private m_arrVarNumbers 27: Private m_varResult 28: 29: Private Sub Class_Initialize() 30: 'Initialize the Numbers member as an empty array 31: ReDim m_arrVarNumbers(-1) 32: End Sub 33: 34: Public Function Init(ByVal arrVarNumbers) 35: Numbers = arrVarNumbers 36: End Function 37: 38: Public Default Function Exec() 39: Dim ix, arrNumbers 40: 41: 'Check if an array was passed. If so, there's nothing to do – exit function 42: If (Not IsArray(Numbers)) Then 43: 'Add your error handling code here 44: Exec = "Invalid data type was supplied to perform the operation." 45: Exit Function 46: ElseIf (UBound(Numbers) - LBound(Numbers) + 1 <= 1) Then 47: 'Array is empty or has single item - Add your error handling code here 48: Exec = "Not enough data was supplied to perform the operation." 49: Exit Function 50: Else 51: arrNumbers = Numbers 52: End If 53: 54: Result = 0 55: For ix = LBound(arrNumbers) To UBound(arrNumbers) 56: If (IsNumeric(arrNumbers(ix))) Then 57: Result = Result + arrNumbers(ix) 58: Else 59: 'Add your error handling code here 60: End If 61: Next 62: Exec = Result 63: End Function 64: 65: Public Property Get Numbers() 66: Numbers = m_arrVarNumbers 67: End Property 68: 69: Private Property Let Numbers(ByVal arrVarNumbers) 70: m_arrVarNumbers = arrVarNumbers 71: End Property 72: 73: Public Property Get Result() 74: Result = m_varResult 75: End Property 76:

Page 4: Advanced QTP

77: Private Property Let Result(ByVal varResult) 78: m_varResult = varResult 79: End Property 80: End Class 81: 82: 'This function behaves as a constructor and returns an initialized instance of the class 83: Public Function GetSum(ByVal arrNumbers) 84: Set GetSum = New Sum 85: GetSum.Init(arrNumbers) 86: End Function 87: 88: 'Test the class 89: Dim objSum, arrNumbers 90: 91: 'Assign an array of numbers 92: arrNumbers = Array(23, 56, 78, 95, 114) 93: 94: 'Get an instance of class Sum and initialize it with numbers array 95: Set objSum = GetSum(arrNumbers) 96: 97: 'Execute Sum (Exec method) 98: objSum.Exec() 99: 100: 'Display result stored in Result property 101: MsgBox objSum.Result, vbOKOnly, "Result from Result Property" 102: 103: 'Alternatively, display the result returned by the Exec method: 104: MsgBox GetSum(Array(23, 56, 78, 95, 114)).Exec, vbOKOnly, "Result from Exec method"

 

The Scripting DictionaryIn VB Script, the scripting dictionary is an object that stores key-item pairs using a hashing algorithm. The items can be accessed using their corresponding keys. Basically, the dictionary is useful to store variant type, non-structured data, such as the catalog number of an item in an inventory list, or the authors of a list of books, and to retrieve the data using the unique keys supplied, as in the following example:

1: Dim dicBooksAuthors 2: 3: 'Create an instance of the scripting dictionary class 4: Set dicBookAuthors = CreateObject("Scripting.Dictionary") 5: 6: With dicBookAuthors 7: 'Add some books (keys) and authors (items) 8: .Add "The Selfish Gene", "Richard Dawkins" 9: .Add "The Mismeasure of Man", "Stephen J. Gould" 10: .Add "The Da Vinci Code", "Dan Brown" 11: 12: 'Answer the question: who wrote "The Selfish Gene"?

Page 5: Advanced QTP

13: strBook = "The Selfish Gene" 14: MsgBox .item(strBook), vbOKOnly+vbInformation, "Author query result for book: " & strBook 15: End With

The example above demonstrates the power of a dictionary as a data storage and data retrieval device. For instance, it eliminates the need for item search functions (direct access) and avoids duplicate key creation.

However, the dictionary can be much more than just a data storage and retrieval device. Thanks to its capacity to store variant data types, it is actually possible to store references to objects of different types. For instance, it is possible to store complex data structures by using nested dictionaries (this issue, however, is out of the scope of the current paper). In the next chapter, we shall delve into a method to exploit this feature of the scripting dictionary object to implement function pointers in VBScript.

Implementing Function Pointers in VB ScriptEarlier in this article, we have shown that it is possible to encapsulate any function as a class by using the command wrapper design pattern. In addition, it has been indicated that the VBScript Scripting Dictionary object is equipped with the capacity of storing variant data types, including references to objects. In what follows a method will be described to combine these two features into a powerful design and programming technique.

Recall that a pointer is a variable that stores the memory address of a function or instantiated class object. Following the example given above in the Command Wrapper section, we shall now show how it is possible to build function pointers using a Scripting Dictionary. Here below, we shall demonstrate the case of the four basic arithmetic operations.The Addition Operation 1: Class Sum 2: Private m_arrVarNumbers 3: Private m_varResult 4: 5: Private Sub Class_Initialize() 6: 'Initialize the Numbers member as an empty array 7: ReDim m_arrVarNumbers(-1) 8: End Sub 9: 10: Public Function Init(ByVal arrVarNumbers) 11: Numbers = arrVarNumbers 12: End Function 13: 14: Public Default Function Exec()

Page 6: Advanced QTP

15: Dim ix, arrNumbers 16: 17: If (Not IsArray(Numbers)) Then 'Not an array, so nothing to do – exit function 18: 'Add your error handling code here 19: Exec = "Invalid data type was supplied to perform the operation." 20: Exit Function 21: ElseIf (UBound(Numbers) - LBound(Numbers) + 1 <= 1) Then 22: 'Array is empty or has single item - Add your error handling code here 23: Exec = "Not enough data was supplied to perform the operation." 24: Exit Function 25: Else 26: arrNumbers = Numbers 27: End If 28: 29: Result = 0 30: For ix = LBound(arrNumbers) To UBound(arrNumbers) 31: If (IsNumeric(arrNumbers(ix))) Then 32: Result = Result + arrNumbers(ix) 33: Else 34: 'Add your error handling code here 35: End If 36: Next 37: Exec = Result 38: End Function 39: 40: Public Property Get Numbers() 41: Numbers = m_arrVarNumbers 42: End Property 43: 44: Private Property Let Numbers(ByVal arrVarNumbers) 45: m_arrVarNumbers = arrVarNumbers 46: End Property 47: 48: Public Property Get Result() 49: Result = m_varResult 50: End Property 51: 52: Private Property Let Result(ByVal varResult) 53: m_varResult = varResult 54: End Property 55: End Class 56: 57: 'This function behaves as a constructor and returns an initialized instance of the class 58: Public Function GetSum(ByVal arrNumbers) 59: Set GetSum = New Sum 60: GetSum.Init(arrNumbers) 61: End Function

 

The Subtraction Operation 1: Class Subtract

Page 7: Advanced QTP

2: Private m_arrVarNumbers 3: Private m_varResult 4: 5: Private Sub Class_Initialize() 6: 'Initialize the Numbers member as an empty array 7: ReDim m_arrVarNumbers(-1) 8: End Sub 9: 10: Public Function Init(ByVal arrVarNumbers) 11: Numbers = arrVarNumbers 12: End Function 13: 14: Public Default Function Exec() 15: Dim ix, arrNumbers 16: 17: If (Not IsArray(Numbers)) Then 'Not an array, so nothing to do – exit function 18: 'Add your error handling code here 19: Exec = "Invalid data type was supplied to perform the operation." 20: Exit Function 21: ElseIf (UBound(Numbers) - LBound(Numbers) + 1 <= 1) Then 22: 'Array is empty or has single item - Add your error handling code here 23: Exec = "Not enough data was supplied to perform the operation." 24: Exit Function 25: Else 26: arrNumbers = Numbers 27: End If 28: 29: Result = arrNumbers(LBound(arrNumbers))- arrNumbers(LBound(arrNumbers)+1) 30: For ix = LBound(arrNumbers)+2 To UBound(arrNumbers) 31: If (IsNumeric(arrNumbers(ix))) Then 32: Result = Result - arrNumbers(ix) 33: Else 34: 'Add your error handling code here 35: End If 36: Next 37: Exec = Result 38: End Function 39: 40: Public Property Get Numbers() 41: Numbers = m_arrVarNumbers 42: End Property 43: 44: Private Property Let Numbers(ByVal arrVarNumbers) 45: m_arrVarNumbers = arrVarNumbers 46: End Property 47: 48: Public Property Get Result() 49: Result = m_varResult 50: End Property 51: 52: Private Property Let Result(ByVal varResult) 53: m_varResult = varResult 54: End Property 55: End Class

Page 8: Advanced QTP

56: 57: 'This function behaves as a constructor and returns an initialized instance of the class 58: Public Function GetSubtract(ByVal arrNumbers) 59: Set GetSubtract = New Subtract 60: GetSubtract.Init(arrNumbers) 61: End Function

 

The Multiplication Operation 1: Class Multiply 2: Private m_arrVarNumbers 3: Private m_varResult 4: 5: Private Sub Class_Initialize() 6: 'Initialize the Numbers member as an empty array 7: ReDim m_arrVarNumbers(-1) 8: End Sub 9: 10: Public Function Init(ByVal arrVarNumbers) 11: Numbers = arrVarNumbers 12: End Function 13: 14: Public Default Function Exec() 15: Dim ix, arrNumbers 16: 17: If (Not IsArray(Numbers)) Then 'Not an array, so nothing to do – exit function 18: 'Add your error handling code here 19: Exec = "Invalid data type was supplied to perform the operation." 20: Exit Function 21: ElseIf (UBound(Numbers) - LBound(Numbers) + 1 <= 1) Then 22: 'Array is empty or has single item - Add your error handling code here 23: Exec = "Not enough data was supplied to perform the operation." 24: Exit Function 25: Else 26: arrNumbers = Numbers 27: End If 28: 29: Result = arrNumbers(LBound(arrNumbers)) * arrNumbers(LBound(arrNumbers)+1) 30: For ix = LBound(arrNumbers)+2 To UBound(arrNumbers) 31: If (IsNumeric(arrNumbers(ix))) Then 32: Result = Result * arrNumbers(ix) 33: Else 34: 'Add your error handling code here 35: End If 36: Next 37: Exec = Result 38: End Function 39: 40: Public Property Get Numbers() 41: Numbers = m_arrVarNumbers 42: End Property

Page 9: Advanced QTP

43: 44: Private Property Let Numbers(ByVal arrVarNumbers) 45: m_arrVarNumbers = arrVarNumbers 46: End Property 47: 48: Public Property Get Result() 49: Result = m_varResult 50: End Property 51: 52: Private Property Let Result(ByVal varResult) 53: m_varResult = varResult 54: End Property 55: End Class 56: 57: 'This function behaves as a constructor and returns an initialized instance of the class 58: Public Function GetMultiply(ByVal arrNumbers) 59: Set GetMultiply = New Multiply 60: GetMultiply.Init(arrNumbers) 61: End Function

 

The Division Operation 1: Class Divide 2: Private m_arrVarNumbers 3: Private m_varResult 4: 5: Private Sub Class_Initialize() 6: 'Initialize the Numbers member as an empty array 7: ReDim m_arrVarNumbers(-1) 8: End Sub 9: 10: Public Function Init(ByVal arrVarNumbers) 11: Numbers = arrVarNumbers 12: End Function 13: 14: Public Default Function Exec() 15: Dim ix, arrNumbers 16: 17: If (Not IsArray(Numbers)) Then 'Not an array, so nothing to do – exit function 18: 'Add your error handling code here 19: Exec = "Invalid data type was supplied to perform the operation." 20: Exit Function 21: ElseIf (UBound(Numbers) - LBound(Numbers) + 1 <= 1) Then 22: 'Array is empty or has single item - Add your error handling code here 23: Exec = "Not enough data was supplied to perform the operation." 24: Exit Function 25: Else 26: arrNumbers = Numbers 27: End If 28:

Page 10: Advanced QTP

29: If (IsNumeric(arrNumbers(LBound(arrNumbers))) And IsNumeric(arrNumbers(LBound(arrNumbers)+1)) And (arrNumbers(LBound(arrNumbers)+1) <> 0)) Then 30: Result = arrNumbers(LBound(arrNumbers)) / arrNumbers(LBound(arrNumbers)+1) 31: Else 32: 'Add your error handling code here 33: Exec = "Invalid data was supplied to perform the operation." 34: Exit Function 35: End If 36: 37: For ix = LBound(arrNumbers)+2 To UBound(arrNumbers) 38: If (IsNumeric(arrNumbers(ix)) And (arrNumbers(ix) <> 0)) Then 39: Result = Result / arrNumbers(ix) 40: Else 41: 'Add your error handling code here 42: End If 43: Next 44: Exec = Result 45: End Function 46: 47: Public Property Get Numbers() 48: Numbers = m_arrVarNumbers 49: End Property 50: 51: Private Property Let Numbers(ByVal arrVarNumbers) 52: m_arrVarNumbers = arrVarNumbers 53: End Property 54: 55: Public Property Get Result() 56: Result = m_varResult 57: End Property 58: 59: Private Property Let Result(ByVal varResult) 60: m_varResult = varResult 61: End Property 62: End Class 63: 64: 'This function behaves as a constructor and returns an initialized instance of the class 65: Public Function GetDivide(ByVal arrNumbers) 66: Set GetDivide = New Divide 67: GetDivide.Init(arrNumbers) 68: End Function

 

Using the Function Pointers to Perform the basic Arithmetic Operations 1: Dim dicFunctionHandler 2: 3: 'Create an instance of the scripting dictionary class 4: Set dicFunctionHandler = CreateObject("Scripting.Dictionary") 5: 6: 'Load some functions 7: With dicFunctionHandler

Page 11: Advanced QTP

8: .Add "+", New Sum 9: .Add "-", New Subtract 10: .Add "*", New Multiply 11: .Add "/", New Divide 12: End With 13: 14: 'Execute the functions using the Function Handler 15: With dicFunctionHandler 16: With .item("+") 17: Call .Init(Array(23, 56, 78, 95, 114)) 18: MsgBox .Exec(), vbOKOnly+vbInformation, "Result (+)" 'Display result returned by the Exec method 19: End With 20: With .item("-") 21: Call .Init(Array(117, 23)) 22: MsgBox .Exec(), vbOKOnly+vbInformation, "Result (-)" 'Display result returned by the Exec method 23: End With 24: With .item("*") 25: Call .Init(Array(7, 5)) 26: MsgBox .Exec(), vbOKOnly+vbInformation, "Result (*)" 'Display result returned by the Exec method 27: End With 28: With .item("/") 29: Call .Init(Array(84, 12)) 30: MsgBox .Exec(), vbOKOnly+vbInformation, "Result (/)" 'Display result returned by the Exec method 31: End With 32: End With 33: 34: 'Or, using the "constructors": 35: 'First, load some functions 36: With dicFunctionHandler 37: .RemoveAll 38: .Add "+", GetSum(Array(23, 56, 78, 95, 114)) 39: .Add "-", GetSubtract(Array(117, 23)) 40: .Add "*", GetMultiply(Array(7, 5)) 41: .Add "/", GetDivide(Array(84, 12)) 42: End With 43: 44: 'Second, execute the functions using the Function Handler 45: With dicFunctionHandler 46: MsgBox .item("+").Exec(), vbOKOnly+vbInformation, "Result (+)" 'Display result returned by the Exec method 47: MsgBox .item("-").Exec(), vbOKOnly+vbInformation, "Result (-)" 'Display result returned by the Exec method 48: MsgBox .item("*").Exec(), vbOKOnly+vbInformation, "Result (*)" 'Display result returned by the Exec method 49: MsgBox .item("/").Exec(), vbOKOnly+vbInformation, "Result (/)" 'Display result returned by the Exec method 50: End With

In the above example we have shown how to:

Page 12: Advanced QTP

1. Implement the Command Wrapper design pattern in VBScript;

2. Implement a “constructor” for a class in VBScript;

3. Implement a function handler using a scripting dictionary;

4. Instantiate such custom classes and load them to a scripting dictionary;

5. Call the loaded function via the dictionary key and retrieve the result.

This is the method suggested in this paper to implement a function pointer in VBScript.

There is also another possible way to call a function implemented with this method, as follows:

1: 'Execute the functions using the Function Handler 2: MsgBox dicFunctionHandler(“+”).Exec(), vbOKOnly+vbInformation, "Result (+)" 3: MsgBox dicFunctionHandler(“-”).Exec(), vbOKOnly+vbInformation, "Result (-)" 4: MsgBox dicFunctionHandler(“*”).Exec(), vbOKOnly+vbInformation, "Result (*)" 5: MsgBox dicFunctionHandler(“/”).Exec(), vbOKOnly+vbInformation, "Result (/)"

and this is because the item property is the scripting dictionary’s default property.In a similar fashion, it is possible to define the Exec methods of the above mentioned classes as Default (by declaring it: Public Default Function) and then the code above can be further reduced to:

1: 'Execute the functions using the Function Handler 2: MsgBox dicFunctionHandler(“+”), vbOKOnly+vbInformation, "Result (+)" 3: MsgBox dicFunctionHandler(“-”), vbOKOnly+vbInformation, "Result (-)" 4: MsgBox dicFunctionHandler(“*”), vbOKOnly+vbInformation, "Result (*)" 5: MsgBox dicFunctionHandler(“/”), vbOKOnly+vbInformation, "Result (/)" 6:

The readers are encouraged to try to execute the sample code shown in this paper, as well as to try the method to implement their own functionality.

 

Performance of Function PointersA small case study comparing the performance of regular function calls with that of function pointers

shows that the more a function is used, the more worthwhile it is to use function pointers.

Number of Calls Single 10 20 50 100Regular calls 0.0943 0.5630 1.0635 2.5806 5.0674Function Pointers 0.1561 0.5308 0.9212 2.0922 4.1219

 

Page 13: Advanced QTP

DiscussionWe have seen so far how to implement the Command Wrapper design pattern in VBScript and a “constructor” for a class in VBScript, as well as a function handler using a scripting dictionary. We have also shown how to instantiate such custom classes and load them to a scripting dictionary, together with different ways to call the loaded function via the dictionary key and to retrieve the result. We have also indicated that this method is, in fact, equivalent to the implementation of a function pointer in C or C++.

The general uses and benefits of function pointers are explained elsewhere (e.g., Lars Haendel, 2005), and hence they will not be covered here. In what remains I shall attempt to convey in which cases the implementation of this design pattern in VBScript in general, and with Quicktest Pro (QTP) in particular, might be of benefit.First, the method presented in this paper should be of great value when the basic hardware configuration is poor, i.e., when the system is low in RAM, by means of dynamic loading of code blocks. Recall that common QTP usage requires the automation developer to add every single function library to the test resources.

Second, by implementing a function handler as illustrated above, it would be possible to build a generic controller component that would enable execution of real keyword-driven scripts. With such a component it would be possible, for example, to define the flow of the different code blocks in an external file, such as an XML file.

Third, the method can be used to emulate callbacks, which is a central feature of function pointers. This can be easily done by passing the function handler entry (dictionary item) to another function.

Fourth, the method can be used to emulate event handling. This can be achieved by returning a string with the name of the function to be called. Please notice that this technique would yield a highly parsimonious coding style, for it makes the need for decision structures to analyze the return code of a function obsolete. An example for this can be found in Appendix 2.

ConclusionThis paper attempted to demonstrate how to implement function pointers in VBScript, and pinpointed the possible uses and advantages of the method in general and particularly for QTP. It is concluded that the technique can help developers to achieve more efficient and generic design and code, and better run-time resources

Page 14: Advanced QTP

management. Future forthcoming articles will further expand on several topics mentioned throughout this paper.

ReferencesHaendel, L. (2005). The Function Pointer Tutorials. Introduction to C and C++ Function Pointers, Callbacks and Functors. Source: http://www.newty.de/fpt/zip/e_fpt.pdfGamma, E., Helm, R., Johnson, R., & Vlissides, J. (1997). Design Patterns: Elements of Reusable Object Oriented   Software . Addison-Wesley Publishing.Appendix 1: Event Handling ExampleWe shall show here how to implement an event handler in VBScript for a typical login flow in QTP. The flow includes three functions: Login, FirstTest and Logout. In addition, two additional functions (AbortTest and Cleanup) are included for error handling and orderly exiting the test flow. Within the TestFlow Sub, the functions are loaded using a generic function (class) loader function (GetFunction), and then called according to their order in the arrRunActions, with their corresponding arguments from arrActionArgs. As can be seen in the code, in case of error the Login.Exec function returns the name of the action to be performed (AbortTest). This action is then performed by means of the statement: strNextAction = dicFunctionHandler(strNextAction)(arrArgs), and since the for loop continues to advance, the normal course of the test flow is altered such that the FirstTest action is never executed. 

1: Class Login 2: Public Default Function Exec(ByVal arrArgs) 3: 'Add your code here 4: If (Not IsArray(arrArgs)) Then 5: MsgBox "Error: username & password are mandatory parameters.", _ 6: vbOKOnly+vbCritical, "Login" 7: Exec = "AbortTest" 8: Else 9: MsgBox Join(arrArgs, ";"), vbOKOnly+vbInformation, "Login" 10: Exec = "" 11: End If 12: End Function 13: End Class 14: 15: Class Logout 16: Public Default Function Exec(ByVal arrArgs) 17: MsgBox "Exec", vbOKOnly+vbInformation, "Logout" 18: 'Add your code here 19: End Function 20: End Class 21: 22: Class AbortTest

Page 15: Advanced QTP

23: Public Default Function Exec(ByVal arrArgs) 24: MsgBox "Exec", vbOKOnly+vbCritical, "AbortTest" 25: Exec = "Cleanup" 26: 'Add your code here 27: End Function 28: End Class 29: 30: Class FirstTest 31: Public Default Function Exec(ByVal arrArgs) 32: MsgBox "Exec", vbOKOnly+vbInformation, "FirstTest" 33: 'Add your code here 34: End Function 35: End Class 36: 37: Class Cleanup 38: Public Default Function Exec(ByVal arrArgs) 39: MsgBox "Exec", vbOKOnly+vbInformation, "Cleanup" 40: 'Add your code here 41: End Function 42: End Class 43: 44: 'This generic function will load any class 45: Public Function GetFunction(ByVal strFunctionName) 46: Execute "Set GetFunction = New " & strFunctionName 47: End Function 48: 49: 'This sub will run the flow 50: Public Sub TestFlow(ByVal arrLoadActions, ByVal arrRunActions, ByVal arrActionArgs) 51: Dim dicFunctionHandler, ix, arrArgs, strNextAction 52: 53: Set dicFunctionHandler = CreateObject("Scripting.Dictionary") 54: Set arrArgs = CreateObject("Scripting.Dictionary") 55: 56: 'Load the required functions 57: With dicFunctionHandler 58: For ix = LBound(arrLoadActions) To UBound(arrLoadActions) 59: .Add arrLoadActions (ix), GetFunction(arrLoadActions (ix)) 60: Next 61: End With 62: 63: 'Run the required flow 64: strNextAction = "" 65: For ix = LBound(arrRunActions) To UBound(arrRunActions) 66: 'Get the action arguments 67: arrArgs = split(arrActionArgs(ix), ";") 68: If (UBound(arrArgs) - LBound(arrArgs) + 1 = 0) Then 69: 'If no args are found, pass an empty string 70: arrArgs = "" 71: End If 72: 'If the next command is empty 73: If (strNextAction = "") Then 74: 'Run the next planned action 75: strNextAction = dicFunctionHandler(arrRunActions(ix))(arrArgs) 76: Else

Page 16: Advanced QTP

77: 'Run the action returned by the previously call action 78: strNextAction = dicFunctionHandler(strNextAction)(arrArgs) 79: End If 80: Next 81: End Sub 82: 83: Call TestFlow(Array("Login", "FirstTest", "Logout", "AbortTest", "Cleanup"), _ 84: Array("Login", "FirstTest", "Logout"), Array("User;123456", "", ""))

Appendix 2: Implementing a Callback with Function PointersWe shall show here how to implement a callback with the classes defined in Appendix 1 and used in this paper. The example used here will be the calculation of the total amount plus the tax on the sum of several items prices.

1: 'Define a constant with the tax value multiplier 2: Const CONST_DBL_VAT = 1.155 3: 4: 'Now, by dynamically loading the functions without the function handler (as shown earlier in this article): 5: 'Execute the (*) function on the result of the (+) function to calculate the tax on an item 6: MsgBox GetMultiply(Array(GetSum(Array(23, 56, 78, 95, 114)), CONST_DBL_VAT)), _ 7: vbOKOnly, "Result (Tax)" 8:

True, the syntax looks complicated, but it is very useful.

RO Vs THE QTP SHELL

Update: this is an article from the old site that wasn’t transferred to the new one. So here it is:In past few days, the question whether it is “Kosher” to change the run-time properties of the AUT (application under test) was given new life in SQAForums (e.g. here). It’s a tough question, that calls for serious discussion.On the one hand, messing with the application’s RO objects has no testing value (not emulating user behavior, backstage behavior should be tested via unit-tests), it could cause serious bugs, and could miss UI bugs a user would have encountered. I think this opinion is quite easy to understand.

On the other hand, when done carefully and selectively, setting RO properties can give us a robust way of doing certian tasks, that could otherwise fail our script. Thus, while not having any testing value by themselves, these task could safely get us to a position where we could do some real testing.

I side with the latter opinion, and I think an example will illustrate why:

Page 17: Advanced QTP

Say our application has some input fields, divided among various tabs, residing within a tab-control. QTP doesn’t recognize this tab-control, and after many attempts to solve the problem elegantly, it becomes obvious that the only method that will work is TabControl.Click X Y.

Now, this sucks, for two main reasons (beside being an ugly solution): It tends to break (Click events are often block by focus shifting, slow system behavior etc.), and it is very VERY demanding from a maintenance point of view (just change the tab-order, add a new tab, change the resolution or the font, and the script breaks). So, this is unacceptable.

OK, so we try thinking outside the box, do a littlie investigating, and find out that when the user switches the active-tab, the tab control’s inner object change its ActiveTab property to the new tab’s index. After a little creative fiddling, we learn that the process works the other way as well – changing the property via QTP, makes the control bring the relevant tab to the front. It suddenly seems very tempting to just set the RO property whenever we want to switch tabs.

Here comes the “carefully and selectively” part (which is REALLY important): we make damn sure that setting the object’s RO property doesn’t break anything. We ask the programmers, run a few tests of ourselves, and after a while, we become confident that no new bugs will be caused by this. Moreover, we ask ourselves if we could miss UI bugs due to working backstage, and answer a reasonable NO (since any bugs will cause the next step of the script to fail).At the end of the day, we can then decide that tab navigation will be done via RO manipulation. We can run through the tab collection, find the tab with the relevant name (By looping the TabControl.Object.Tabs collection), get its index, and switch. It doesn’t test the tab control, but it makes sure we’ll get to the right tab, where we could rip the application apart, good-old-fashion QTP style.

So, to sum-up, we gained:A. A much more robust script (no .Click breaking the script)B. A generic script (serves multiple systems with various fonts, resolutions etc.)C. Zero-maintenance growth and change (handles “out of the box” New tabs, different tab ordering, different tab layout)We paid for it with:A. VERY small possibility for missing tab-switching UI bugs due to not actually pressing the tab control.

Page 18: Advanced QTP

B. VERY small possibility for creating new tab-switching bugs due to messing around with the RO objects.And this seems like a pretty good balance to me.

Just to be clear – I agree we do need to be selective and careful about applying this methond (e.g. NEVER on a data-grid control), but we shouldn’t abandon it all together.You can read more about RO manipulating techniques in our article about automating custom controls.

Implementing a GUI Layer with Classes

AbstractThis article describes a powerful technique that exploits Object Oriented Design Patterns, QTP descriptive programming (DP) and the Dictionary object to pack together GUI objects with their corresponding business oriented functions. The article includes a valuable bonus: a highly effective tip that enables to exit a test smoothly, preventing QTP from getting stuck when it fails to identify GUI objects during runtime.

 

IntroductionA central issue in automation development is how to reduce the effort spent on maintenance. Questions such as: “Should we use an Object Repository (OR) or Descriptive Programming (DP)? if an OR is chosen, then should we use a shared OR or a local OR for each Action? If DP is selected, then what is the optimal way to implement it?” are quite ubiquitous and the answers given can vary depending on the particular characteristics of the project, but many times also on the individual personalities involved.

In this article I will analyze the concept of Test Objects in the context of Object Oriented principles. I shall attempt to show that QTP’s Object Repository does not fit well with such principles and what is the impact of this on automation development viz-a-viz cost-effectiveness and maintainability. Subsequently, I will describe an expansion of the OR concept that is compatible with OO principles – the GUI Layer. This concept has been adopted by SOLMAR’s automation experts based on the observation that it is possible to maximize code reusability (and thus boosting maintainability) by undertaking an OO approach that breaks an automation project into several abstraction levels.

Page 19: Advanced QTP

 

Test Objects and Run-Time ObjectsA test object encapsulates a reference to a run-time object – the real world object that is instantiated in the application under test (AUT). A test object stores the description of the referenced object, which is a set of attributes and values that serve as criteria to locate the right object during the test run session. This description is pretty much like the one we keep in mind to identify our blind date partner at the meeting time (“I’m brunette, slim and tall, and will be wearing a red dress and shoes”, you’ve been told on the phone).Similarly, QTP uses such a description to query the OS if there’s an object currently loaded to the machine’s RAM. If the answer is positive, the OS returns the address of the object (a reference or pointer) and hence QTP gains access to the object’s public methods and properties, since the private methods and properties are hidden from view by definition (so QTP never makes progress to the private domain, as you would with the brunette, if lucky!).Though this description may seem overly simplified, basically it is this process that enables us to interact with the GUI (and other) objects using QTP or other automation tools (Test Partner, Test Complete, Rational Robot, AutoIt, etc.). A more detailed account of the distinction between Test Objects and Run-Time Objects is available in Yaron Assa’s article “Differences and Connections between Runtime Objects and Test Objects” (2009). 

Test Objects and the Object RepositoryThe above account of test objects makes it seem only natural to think of them as data entities. What do I mean by this? Based on a description composed of a set of properties and values, QTP is able to identify a GUI object among all the objects currently loaded to the machine’s RAM. This is similar to the retrieval of a particular record from a database table based on a SQL WHERE clause. The difference is that in this case, it is the OS (Windows), not the DB, the one that returns the record. Moreover, this record contains a single field that stores a reference to the actual runtime object. Following this line of thought, it seems only natural to have these entities – Test Objects – stored in a database, which essentially is exactly what the Object Repository is (a side note, if you noticed the bdb suffixed files in QTP tests, then note that bdb actually stands for Berkeley Data Base – an Oracle product).

Page 20: Advanced QTP

What, then, seems to be the problem with the OR approach? Tests Objects are really data entities, but at the end of the day we need to perform actions on Runtime objects, insomuch that the above account leaves us with an incomplete picture. I will explain below why.

It is true that the OR, if used properly, reflects a best practice according to which we ought to store the description of each GUI (Test) Object only once to reduce maintenance costs. However, in order to achieve this a great effort must be invested also into managing the automation project properly, because ad-hoc requests and resource limitations can endanger the integrity of such a precious resource. For instance, suppose that two automation developers work simultaneously with a shared repository, but one is maintaining the scripts for the last version and the other is developing new scripts for the newest one. Given that GUI changes are made quite regularly from one AUT version to the next, it will not be too long until the OR becomes cluttered with new, redundant, test objects required by the developer working on the newest version. And this is a relatively good result. The worst case would be one where each developer would change in turn the descriptions of existing objects to match their requirements, damaging irrevocably the ROI on the automation project as a whole. Of course, there are solutions to such situations, such as keeping separate versions of the OR that match each AUT version and using configuration management software in the automation project. But, as aforesaid, this requires good project management, which is not always available.Moreover, keeping versions of the OR leaves one issue still open. Because automated tests are, in general, regressive by nature, then maintenance would certainly pertain not only to the OR but also that the code that implements the actual GUI manipulations and verifications, as well as the input data and expected results data. If the common situation would have been that only the object descriptions changed from one AUT version to the next, then using an OR would be an excellent solution. However, more often than not, GUI changes reflect modifications in the way the AUT actually functions, which are many times also accompanied by even deeper changes – in the database structure, for example. So, changes in the AUT would require matching changes in both the OR and the scripts that refer to its test objects, as well as in the data sources that feed these scripts. Taking into account the fact that scripts are most of the time maintained, not developed, the aforementioned line of analysis lead me to the conclusion that something in the OR concept is faulty regarding the manageability of a large-scale automation project.

Page 21: Advanced QTP

 

The Object Oriented ViewThe Object Oriented software paradigm is based on the basic concepts of encapsulation, inheritanceand polymorphism. Encapsulation basically means to pack together functionality and data structures that belong together, and the package through which we achieve this is called a class. In fact, classes mostly are representations of data entities (like Customer, Product, etc.). The difference between a fully featured class and a mere data structure (as in the C programming language) is that the class also wraps the functions available for use with the class data fields. These are commonly referred to as the classmethods. For example, a typical class for a Customer would pack the data fields that define the customer as a data entity (customerId, firstName, lastName, phoneNo, etc.) together with functions such assetCustomerId, getCustomerId, which assign and retrieve the value of the respective field, as well as others like getCustomerAge and getCustomerBalance which would perform a calculation based on the class fields current values and then return the result.Inheritance is deeply related to one of the major goals of the OO approach – reusability. In OO programming languages like C++ and Java, reusability comes into play by means of the ability to device a new class that “inherits” the fields and methods of a previously defined class (also called a base class) and expands on these according to the specific requirements that raised the need for the new class. Polymorphism is yet another powerful concept that enables one to design more than one version of a function (with the same name but different number or types of arguments) to be able to handle transparently different situations within the application context. For instance, we might need to handle numbers of different types (float, int) and so instead of having a single function with conditional statements that check for the value type that was passed, we’ll have several functions with the same name but different signatures (different type of arguments). Hence in our code that uses these functions we would not need to use casting; we would use the same interface in both cases, with the correct process ultimately invoked by the runtime environment. However in QTP these two last concepts of inheritance and polymorphism cannot be implemented based on the VB Script language which provides very rudimentary support for working with classes. Nevertheless, we shall see later that this is not a reason to refrain from using classes in testing automation.

Page 22: Advanced QTP

The public and private notions mentioned above are of great importance with regard to encapsulation and inheritance. They enable the code developer to determine which fields (also called properties or attributes of the class) and methods will be actually accessible to the world outside the class (public), and which will remain for internal (private) use of the class members (i.e., the class fields and methods). The OO methodology is not new and has been widely put into practice in the software design and development industry. It is recognized as one approach that makes the resulting code more reusable, maintainable, readable, scalable, extensible and, yes, also more testable (as huge pieces of functionality are broken down into smaller, self-contained capsules or packages). Of course, making good use of the methodology requires deep analytical skills that enable the code designer to infer correctly from the requirements of the software under construction which are the appropriate entities involved and how they are interrelated.How this relates to the way we approach, or should approach, the testing automation challenge? Let us delve into it below.

 

Testing Automation and Software DevelopmentOne thing I have witnessed throughout my career is that QA professionals of all levels treat testing automation as just another testing activity. Not surprisingly, it is not rare to find automation professionals that think likewise since many times the role of automation. Sometimes this is even reflected by the job title given to the automation staff: “automatic tester” is one of the absurd titles I have encountered several years ago. This reflects a misunderstanding of the role of the Automation Developer and, as I shall explain in what follows, this belief is a true misconception of the essence of testing automation.

The task of automating tests should not be treated differently than any other content specific automation task. Generally speaking, computer programs that perform operations instead of humans implement automation. So basically any block of code is a kind of automation device, or robot. Scripts that carry out operations on GUI objects – such as QTP tests – are not different than any other piece of software. Put it in other words, a testing automation project is, indeed, a specific kind of software development project. As with any software product, an automation project also has its own SRS (Software Requirements Specifications) document: the STD (Software Test Design) “document” (or tests design in a quality management tool such as HP’s Quality Center

Page 23: Advanced QTP

or Orcanos’ QPack), which should guide the automation developer in the implementation of the required code.

If so, then an automation project should be treated and managed as any other development project and, in fact, even more so. This is because the automation developer faces challenges that are usually not felt by the developers of the AUT. Some of the main challenges are the following:

1. First, the automation developer must have an overall view of the AUT, as the automation scripts may cover a large part of the AUT’s main functionality. Members of the AUT’s development team do not necessarily need this, as the division of labor among the different teams is typically coordinated by the team leaders and a project manager. So a developer can focus on the part assigned to him and do it well even without having in depth knowledge of the whole system.

2. Second, the automation scripts should be mapped to the test design, as they should emulate the steps done by a human tester. But, quite often, automation developers find out that the test design leaves many open issues that need to be resolved beforehand, as the automation scripts do not possess the flexibility and ingenuity of a human tester during runtime.

3. Third, the GUI controls used by the development team, and also the AUT’s behavior may pose a real technological challenge regarding the identification of the objects by QTP (or other tools). Many times solutions that extend the basic capacities of the tools are required, as is the case with third-party and custom controls.

 

Following the above exposition, I think we may conclude that an automation project should be definitely managed as a development project. If this is so, why should one fall again and again victim to the fallacy of treating an automation project as a trivial task (does “record-replay” remind you of something?), where the opposite is true? Why not, then, approach to do it using the most widely accepted development method – Object Oriented – to attain the best possible results? In what follows I will describe a method to implement automation “scripts” based on an extension to the OR concept – the GUI Layer, which is based on solid OO principles. 

The Concept of LayersI hope to have managed so far to make clear why the OR approach, with scripts spread all around the place, is far from being the optimal approach. Now, following the above discussion, let us take a look at the concept of developing code in layers, especially the GUI Layer concept. In general, layers are useful to maximize reusability (recall the previous discussion on OO principles). I shall define a GUI Layer as a set of classes that pack (encapsulate) together the interfaces required to manipulate the AUT’s GUI

Page 24: Advanced QTP

objects in each application context. In other words, it is a set of classes that bridge between the AUT’s GUI (i.e., the test objects proper) and the Business or Application Layer, of which we shall have more to say later. In a sense, may be it would be more appropriate to call it the GUI-Business Bridge Layer, but it is already accustomed among the experts to call it a GUI Layer, to keep it short. I will illustrate below how such a layer can be built, and what are the benefits gained from adopting such an approach to testing automation.Implementing the GUI LayerEncapsulating Test Objects in ClassesLet us take a testing automation project on a typical AUT and see how the solution should be designed according to the approach outlined above. The first step would be to make a simple list of all application GUI contexts – i.e., the windows (pages in a Web application), dialogs, and popup messages. For each of these entities, which are containers of other GUI objects, we define a class, for example:

Class LoginEnd ClassClass MainWindowEnd ClassClass CreateCustomerEnd Class

and so on for each of the application contexts. Because QTP does not allow for direct instantiation of classes defined in external vbs files with the operator New, it is also required to define the following function that will return an instance of a GUI layer class (a kind of constructor function), as follows:'-------------------------------------------------------------------------------Public Function CreateLogin()'-------------------------------------------------------------------------------'Function: CreateLogin'Creates an instance of the Login class''Remarks:''Arguments:' N/A''Returns:' Object - As Login''Owner:' John Doe''Date:' dd-MMM-yyyy'

Page 25: Advanced QTP

'------------------------------------------------------------------------------- Dim objLogin Set objLogin = New Login Set CreateLogin = objLogin'-------------------------------------------------------------------------------End Function'-------------------------------------------------------------------------------

The second step is, obviously, to define the members of each class. Now, since each class is, as aforesaid, a container of other GUI objects, we shall make use of the Scripting.Dictionary to store the references to the test objects contained in the window, dialog or page. (the  Dictionary object has been already extensively discussed in other articles published at AdvancedQTP‘s knowledge base). So, the first member I shall introduce here will be common to all GUI Layer classes, and I will define it as m_htChildObjects, for example:Class Login Private m_htChildObjects 'As Scripting.DictionaryEnd ClassClass MainWindow Private m_htChildObjects 'As Scripting.DictionaryEnd Class

and so on for each of the application contexts (ht stands for HashTable, which is what the dictionary really is). The private member m_htChildObjects will be accessed through the class property ChildObjects. This property is defined as follows:'-------------------------------------------------------------------------------'Property: ChildObjects'Get and Set the m_htChildObjects member field''Remarks:' R/W''Arguments:' dic''Returns:' m_htChildObjects As HashTable''Owner:' John Doe''Date:' dd-MMM-yyyy''-------------------------------------------------------------------------------Public Property Get ChildObjects()'------------------------------------------------------------------------------- Set ChildObjects = m_htChildObjects'-------------------------------------------------------------------------------End Property'-------------------------------------------------------------------------------

Page 26: Advanced QTP

'-------------------------------------------------------------------------------Public Property Let ChildObjects(ByRef dic)'------------------------------------------------------------------------------- Set m_htChildObjects = dic'-------------------------------------------------------------------------------End Property'-------------------------------------------------------------------------------

The third step is to define the objects contained within each context. For this purpose, I will define a public method within the class called Init, as follows:'-------------------------------------------------------------------------------Public Function Init()'-------------------------------------------------------------------------------'Function: Init'Initializes the context and child objects''Dependencies:' IsContextLoaded(htContext)''Remarks:' N/A''Arguments:' N/A''Returns:' True/False''Owner:' John Doe''Date:' dd-MMM-yyyy''------------------------------------------------------------------------------- ChildObjects = CreateObject("Scripting.Dictionary") With ChildObjects .Add "Browser", Browser("name:=My App") .Add "Page", ChildObjects("Browser").Page("title:=My App \- Login") .Add "Username", ChildObjects("Page").WebEdit("html id:=Username") .Add "Password", ChildObjects("Page").WebEdit("html id:=Password") .Add "Submit", ChildObjects("Page").WebButton("outertext:=Submit") End With 'IsContextLoaded is a function that iterates through the Dictionary and checks if the GUI objects "exist" Init = IsContextLoaded(ChildObjects)'-------------------------------------------------------------------------------End Function'-------------------------------------------------------------------------------

 

Page 27: Advanced QTP

The code snippet above shows a typical Init method for a GUI Layer class for a Web application Login page. The test objects are added as entries to the ChildObjects Dictionary, and their identity is defined using Descriptive Programming (DP). The reader can easily infer the analogy to the OR. It is thanks to this encapsulation method that we ensure that GUI objects are always defined at a single place. At the end of the function body you will notice that it returns the result of a call to the function IsContextLoaded which accepts as argument the Dictionary that stores the ChildObjects.IsContextLoaded is defined in a separate common library, as follows:'-------------------------------------------------------------------------------Public Function IsContextLoaded(ByRef htContext)'-------------------------------------------------------------------------------'Function: IsContextLoaded'Checks that the current GUI context is loaded''Iterates through the htContext (HashTable) items and executes the Exist method with 0 (zero) as parameter.''Remarks:' N/A''Arguments:' ByRef htContext - As HashTable''Returns:' True/False''Owner:' Meir Bar-Tal, SOLMAR Knowledge Networks Ltd.''Date:' 11-Nov-2008''See Also:''------------------------------------------------------------------------------- Dim ix, items, keys, strDetails, strAdditionalRemarks '--------------------------------------------------------------------------- items = htContext.Items keys = htContext.Keys For ix = 0 To htContext.Count-1 IsContextLoaded = IsContextLoaded And items(ix).Exist(0) strDetails = strDetails & vbNewLine & "Object #" & ix+1 & ": '" & keys(ix) & "' was" If IsContextLoaded Then intStatus = micPass strDetails = strDetails & "" strAdditionalRemarks = "" Else intStatus = micWarning

Page 28: Advanced QTP

strDetails = strDetails & " not" strAdditionalRemarks = " Please check the object properties." End If strDetails = strDetails & " found." & strAdditionalRemarks Next '--------------------------------------------------------------------------- Reporter.ReportEvent intStatus, "IsContextLoaded", strDetails'-------------------------------------------------------------------------------End Function'-------------------------------------------------------------------------------

And it returns True if all objects defined in the Dictionary are identified, or False if at least one object is not found. This function is generic and it is used in the projects I manage to ensure that QTP does not get stuck while attempting to perform some operation on a non-existing GUI object. Another benefit of this method is that it points exactly to the object we need to recheck and update, making maintenance much easier.

Encapsulating Business Methods in ClassesThe next step after defining the child objects of the GUI context is to define the operations required to perform the application or business scenarios within the given context. This is easily done by implementing class methods. For example, the login class outlined above would need the following methods to begin with: SetUsername, SetPassword and Submit. These are shown below:'-------------------------------------------------------------------------------Public Function SetUsername()'-------------------------------------------------------------------------------'Function: SetUsername'Set the Username field''Dependencies:' N/A''Remarks:' N/A''Arguments:' N/A''Returns:' N/A''Owner:' John Doe''Date:' dd-MMM-yyyy''------------------------------------------------------------------------------- ChildObjects("Username").Set GlobalDictionary("Username")

Page 29: Advanced QTP

'-------------------------------------------------------------------------------End Function'-------------------------------------------------------------------------------'-------------------------------------------------------------------------------Public Function SetPassword()'-------------------------------------------------------------------------------'Function: SetPassword'Set the Password field''Dependencies:' N/A''Remarks:' N/A''Arguments:' N/A''Returns:' N/A''Owner:' John Doe''Date:' dd-MMM-yyyy''------------------------------------------------------------------------------- ChildObjects("Password").Set GlobalDictionary("Password")'-------------------------------------------------------------------------------End Function'------------------------------------------------------------------------------- '-------------------------------------------------------------------------------Public Function Submit()'-------------------------------------------------------------------------------'Function: Submit'Presses the Submit button''Dependencies:' N/A''Remarks:' N/A''Arguments:' N/A''Returns:' N/A''Owner:' John Doe''Date:

Page 30: Advanced QTP

' dd-MMM-yyyy''------------------------------------------------------------------------------- ChildObjects("Submit").Click 'TODO: Verify data submission performed successfully'-------------------------------------------------------------------------------End Function'-------------------------------------------------------------------------------

Notice the use of the GlobalDictionary to retrieve the values required by the functions, and the use of the ChildObjects property to retrieve through the test object a reference to the runtime object.

The next step would be to move on to the Business Layer, which implements an application scenario building on the strong foundations of the GUI Layer. For instance to perform the Login function based on the above example, we could wrap it with the following function:

'-------------------------------------------------------------------------------Public Function do_login()'-------------------------------------------------------------------------------'Function: do_login'Implements the business logic of the do_login Action.''Remarks:''Arguments:' None''Returns:' Status''Owner:' John Doe''Date:' dd-MMM-yyyy''------------------------------------------------------------------------------- Dim intStatus, objLogin Set objLogin = CreateLogin() If objLogin.Init() Then objLogin.SetUsername() objLogin.SetPassword() objLogin.Submit() 'If login succeeds intStatus = micPass Else intStatus = micFail End If do_login = intStatus'-------------------------------------------------------------------------------

Page 31: Advanced QTP

End Function'-------------------------------------------------------------------------------

 

Notice the use the Login class outlined above and of its Init function as a precaution to ensure the GUI context is loaded, and not get stuck, as mentioned above. As you can also see, the code within the function above is easy to understand, and is not cluttered with the references to both the OR test objects and data sources as quite often is the case. If changes are made to the GUI objects of a given context, then the changes will be concentrated within a single package, both with respect to the object properties and to the functionality required to manipulate the context’s child objects. Yet another gain from this method is standardization. By implementing code this way we achieve a high degree of homogeneity in the code written by different developers, and thus enhancing the manageability of the automation project.An advanced alternative to the last example is to pack such a Business Layer function using the Command Wrapper Design Pattern, as outlined in my article Function Pointers in VB Script (revised). For example:'VB Script DocumentOption Explicit'-------------------------------------------------------------------------------Class do_login'-------------------------------------------------------------------------------'Class: do_login'Encapsulates the do_login Action.''Remarks:''Owner:' John Doe''Date:' dd-MMM-yyyy''------------------------------------------------------------------------------- '------------------------------------------------------------------------------- 'Methods '------------------------------------------------------------------------------- '------------------------------------------------------------------------------- Public Default Function Run() '------------------------------------------------------------------------------- 'Function: Run 'Implements the business logic of the do_login Action. ' 'Remarks: ' 'Arguments: ' None

Page 32: Advanced QTP

' 'Returns: ' Status ' 'Owner: ' John Doe ' 'Date: ' dd-MMM-yyyy ' '------------------------------------------------------------------------------- Dim intStatus Set objLogin = CreateLogin() If objLogin.Init() Then objLogin.SetUsername() objLogin.SetPassword() objLogin.Submit() 'If login succeeds intStatus = micPass Else intStatus = micFail End If Run = intStatus '------------------------------------------------------------------------------- End Function '-------------------------------------------------------------------------------'-------------------------------------------------------------------------------End Class'-------------------------------------------------------------------------------

Adopting this method would enable the implementation of an advanced generic controller that loads its flow from an external data source such as an XML file. Such a controller device was developed by me together with my partners at SOLMAR Knowledge Networks as part of our generic Object Oriented comprehensive automation framework – My System.SummaryThis article has reviewed an alternative approach to code implementation in a testing automation project that is based on object-oriented principles. I have shown that automation scripting should not be treated differently than software development. Moreover, I tried to convey that logically following this line of thought leads to the conclusion that an automation project must be approached as a software development project par excellence, and suggested an expansion to the OR concept – which was shown as inadequate viz-a-viz the OO paradigm – to encapsulate the interfaces to the GUI objects: the GUI Layer. The article has provided a practical example of how to implement such a layer and how to invoke it using a Business Layer, and explained in detail the benefits of adopting this approach to obtain the maximum ROI from an

Page 33: Advanced QTP

automation project regarding maintainability, readability, scalability, extensibility and testability. Future articles will expand on this theme and guide the readers into how to gain from the implementation of other design patterns within the framework laid out in this article.

Differences and Connections between Runtime Objects and Test ObjectsThere is a lot of confusion about how the concepts of Runtime Objects andTest Objects are used in QTP. The general idea is usually clear enough, but when it comes down to the actual details, sometimes even the experts do not quite know if using the GetROProperty method on a TO will work.This article will guide you through the general concepts, and explain all the nitpicking involved in all the commands and practices relevant to the matter.

The QTP Test Object ShellUnder their fancy name, Test Objects refer to your everyday QTP entities. VBCheckbox, Window, WebTable, Browser – these are all QTP constructed test objects, which represent real-world application objects “out there” (i.e., in our application under test).

Whenever we record a real-world object (also known as a Runtime object), QTP creates a corresponding Test Object and stores it in the Object Repository. The Test Object contains QTP specific properties and methods which allow us both to identify the relevant runtime object during playback, as well as to perform standard operations on it and with it.

For example, a Window test object contains properties which allow QTP to identify it in the “real world”: text, height, nativeclass, etc.; on top of these, it also contains methods to operate it: Close, Click, Maximize. We can easily see these methods and properties via the object spy’s Test Object tab.It is important to understand that the test object’s properties and methods are not really “out there” in the application’s runtime object, but are only derived from it. So for example, a SwfComboBox Test Object has the property “all items”, while the actual .net application combobox runtime object has no such property. QTP builds the “all items”

Page 34: Advanced QTP

property out of the combobox runtime object’s available items collection, so even though the property does not exist in the runtime object, it is derived from it.Test Objects rolesTest objects play several roles in QTP, the most immediate one is object identification. By using a window test object with a text property of “Notepad”, we tell QTP to look for a runtime window object with text = “Notepad”.

Another important role is standardization. While the application runtime objects might be extremely complex, the corresponding QTP test objects are standard and very simple in comparison. The test object’s simple properties and methods act as a bridge, sparing us the need to nitpick our way through the runtime object’s unknown commands, events and properties. QTP will translate the test object’s simple commands to the runtime object’s commands required to perform the task at hand.

For example, selecting a value in a .Net combobox usually involves changing several properties (SelectedIndex, SelectedItem), and raising several events (SelectionChanged, ItemSelected, TextChanged, etc.). However, the corresponding SwfComboBox test object provides a much more simpleand standard “Select” method which does the trick for you.Working with Test ObjectsHow can we work with test objects in our scripts?

The most straight-forward way is to simply use them. Every time we maximize a window, select a value from a VBComboBox or click a WebElement, we do it through the QTP test objects. QTP will then locate the corresponding “real” runtime object, and perform the relevant action on it. We can either use prerecorded test objects from the object repository, or create instant test objects via Descriptive Programming.Aside from straightforwardly using our test objects to perform actions, there are 4 special commands which allow us to access the core of QTP’s test object mechanism: GetTOProperty, GetTOProperties, SetTOProperty and GetROProperty.

GetTOProperty allows us to retrieve the value of a test object property. For example, this command will print out “Notepad”:Msgbox Window("text:=Notepad").GetTOProperty("text")

The GetTOProperty command will retrieve the value as it was originally recorded (or created via DP). It does not matter if the corresponding runtime object exists, or if that value was updated in “the real world” since the object was recorded.

Page 35: Advanced QTP

GetTOProperties returns a collection with all the test object properties. Like GetTOProperty, the GetTOProperties command has nothing to do with the actual runtime object in our application, only the test object which represents it.GetROProperty allows us to get the current value of a test object property. This means that unlike the GetTOProperty and GetTOProperties commands, GetROProperty requires the test object’s corresponding runtime object to exist. QTP will touch the runtime object, and retrieve the value of the property from it. For example, if the text of the Notepad window changed from when that object was recorded, this command will retrieve the updated value:Msgbox Window("Notepad").GetROProperty("text")

Unlike GetTOProperty and GetTOProperties, it does not even matter if the test object originally recorded the relevant property. In the following example, our test object has only one property – nativeclass, but GetROProperty will still be able to retrieve the value of the window’s text property (where GetTOProperty would return an empty result).Msgbox Window("nativeclass:=Notepad").GetROProperty("text")

It is important not to be confused by the “RO” in the command name. Even though it has RO (Runtime Object) in it, the command still retrieves only test object properties. So even though a .Net combobox runtime object does not have an “all items” property, you could still execute the following command, and it will print out all the available items:Msgbox SwfWindow("X").SwfCombobox("Y").GetROProperty("all items")

Last but not Least, SetTOProperty allows us to change a test object’s property for the duration of the test-run. Any changes made with SetTOProperty will be reset once the test-run had finished, or even when you go in and out of actions. It is important to note that changing a test object’s properties might cause QTP not to identify its corresponding runtime object. So for example:Call Window("Notepad").SetTOProperty("nativeclass", "incorrect value")Window("Notepad").Close 'this command will fail

The second command will fail as QTP will search for an object with nativeclass = “incorrect value”, and it will not find any object matching that description.From Test Objects to Runtime ObjectsTest objects are the cornerstone of all QTP object operations, but they are sometimes too limited for the task at hand. There are two situations in which we might prefer working with the actual runtime objects of the application, as opposed to the QTP test objects:

Page 36: Advanced QTP

There are some objects (especially .Net custom controls), which QTP fails to recognize correctly. For example, some combo-boxes might appear as SwfObjects, or generic ActiveX controls. As QTP fails to recognize the object for what they are (in this example, combo-boxes), the test objects it creates do not have the properties or methods that are required to operate the objects (like “all items” and Select).

Another case is when we are required to perform some non-standard operation with an object. Here it makes no difference if QTP succeeds to correctly recognize the object, as the corresponding test object will only provide us with a standard set of commands and properties. A good example for this case is trying to find the font style of a WebElement. The WebElement test object simply does not carry the information we need, and so we have to resort to unusual measures such as working with the WebElement’s runtime object.

Working with Runtime ObjectsYou can see an object’s runtime properties and methods in the RO tab of the object spy. If it is a .netobject, you can also use the .Net form spy to get a more detailed look of those properties that contain inner complex objects.Not all test objects support working with their corresponding runtime objects, but if they do, it can be done via their .Object property. This property exposes all the properties and methods of the runtime object “hiding” behind our test object. For example:

Print Browser("X").Page("Y"). WebList ("z").Object.selectedIndex

This command will find the runtime object corresponding to the WebList’s test object, access its selectedIndex property, and print its value. Of course that in order for this to work the runtime object must exist when we execute the command. It is important to note that WebList test object does not have a selectedIndex property, only a derived property called “selected item index”. So in this case, these commands will print the same value:Print Browser("X").Page("Y"). WebList ("z").Object.selectedIndexPrint Browser("X").Page("Y"). WebList ("z").GetROProperty("selected item index")

The true benefit of working with runtime objects lies with properties and methods that are not available through the QTP test object. So for example, we can find out the color of a WebList’s items text:Print Browser("X").Page("Y"). WebList ("z").Object.CurrentStyle.Color

We can even use the runtime object’s methods, and perform operations that are otherwise unavailable to us:

Page 37: Advanced QTP

Print Browser("X").Page("Y"). WebList ("z").Object.focus

By accessing the runtime world, we can perform almost any operation on our objects, regardless of QTP’s ability to recognize them for what they truly are. So even if our .net combobox is identified as a SwfObject, we’ll still be able to select an item from it, by overwriting the runtime property of the selected item’s index.SwfWindow("X").SwfObject("Y").Object.SelectedIndex = 1

With all their benefits, it is important to remember that working directly with runtime objects can have significant downsides. Unlike the standard and simple test objects, it is unclear how to operate the object, and exactly what properties and methods it has (CurrentStyle.Color was hard to come by). Even when we find the right properties or methods, using them “from behind the scenes” may cause application errors and invoke hidden bugs.There are ways to overcome these shortcomings, such as exploring custom controls, and carefully mapping the consequences of changing runtime properties, but nonetheless, we must always be aware of them.

Adding a Call-Chain Mechanism to QTP

Background and motivationA common frustrating experience is having your script pop-up an error dialog in some long-forgotten function, without one having the slightest clue in its regard. What usually follows is a series of trial-and-error breakpoint runs to determine which action the script was actually trying to perform when executing the function call that triggered the error.

All programming languages suffer from such problems, but in late bound languages like VB Script this problem is really challenging. This is because in VB Script compilation is done on the basis of a line-after line interpretation during run-time, in contrast to languages such as C/C++, in which compilation at least guarantees that the function calls are syntactically correct.

For example, a faulty call to a function (wrong function name or number of parameters) would pass unnoticed in VB Script when part of a function, whereas in C++ it would trigger a compilation error.

Page 38: Advanced QTP

The only way to get the error message in such cases is by actually performing the code, that is, calling the wrapping function, or by testing the inner function call on a standalone basis. In any language, it is not long until the calls to functions and object methods become too deeply nested for us humans to grasp.

Having said that, programmers do debug their code, even when bugs and error occur within extremely nested function calls. They manage to do so via a dedicated mechanism: the call-chain (sometimes referred to as the call-stack).The call-chain shows us exactly which function called the currently executing function, which function called that function, and so on up to the root of the call hierarchy. This allows us to analyze the error’s path, and see where things started to go wrong (usually 2-3 function calls before the function we are currently in).An example of a Visual Studio call-stack for an error can be viewed in Figure 1 below. Please notice that it shows both the functions and code lines that precipitated the error.

Figure 1: A Sample Visual Studio Error Call-Stack 

Just imagine how many debug runs you could have saved just by knowing the exact order of calls which lead you to the current function. Or, how valuable would it been to know which parameters were sent into those function calls.

Possible ImplementationsNow, it is possible to implement a call-chain in QTP with intensive reporting – just by sending a line to the report from any function as you enter and exit it. However, this will render your report cluttered and hence difficult to grasp. Moreover, QTP reports are inaccessible during the run-session, and that is exactly why the call-chain data is crucial.

Is the inevitable conclusion that there is no way to implement a call-chain in QTP effectively?

Well, the answer is no. There is a way, indeed, though it’s not trivial. A call-chain mechanism is just one of the rich features provided by SOLMAR’s flagship product: My SystemTM. Developed by our people at SOLMAR’s R&D Division, My SystemTM brings the latest advances to boost ROI on testing automation (We shall have much more to

Page 39: Advanced QTP

say on that in future posts; stay tuned!). Meanwhile, I thought that I could share some of my techniques and thoughts on the subject.Though the examples given here are not meant to be used or to be an out-of-the-box solution, I do believe they can point you in the right direction for developing robust solutions of your own.

 

The ConceptDeveloping an effective call-chain requires four basic components:

1. A place to store the call-chain data.

2. A mechanism for updating the call-chain when you enter a context (by context I mean a function, sub or action).

3. A mechanism for updating the call-chain when you exit a context.

4. A mechanism for viewing the stored data mid-run (we won’t get into this one – it is basically just a simple window to peek at the stored data)

If you have a robust mechanism for updating the call-chain when you are exiting and entering contexts, and an easy to use storage for the call chain, then implementing the call-chain will be quite straightforward.

 

Storing the Call-Chain DataAn excellent mechanism for storing call-chain data is a stack. A stack can store data (i.e., the names of the contexts you happen to enter during the flow) in a FILO manner – First-In Last-Out. This will make sure your call chain contains all the relevant data needed to understand what got you until this point, and nothing else. Here is an example of how the stack mechanism would work:

Let’s say our code went though these functions:

Function Func1 Call Func2End FunctionFunction Func2 Call Func3End FunctionFunction Func3 Dim x

Page 40: Advanced QTP

Call Func 4 x = 1/0End FunctionFunction Func4 'EndedEnd Function

If we call Func1, it will call Func2, which would call Func3, and it would call Func4. At this point, our call-chain stack would look like this: (“Func1”, “Func2”, “Func3”, “Func4”). Now Func4 will end, returning the control to Func3. Since our call chain is a Stack, once the code will exist Func4, it will remove the last item from our stack, make it: (“Func1”, “Func2”, “Func3”).Line 14 will result is an error, where we could debug, and take a look at our stack, which would hold just enough data to understand how we got into Func3, and no irrelevant data (for example, the fact the we have entered and exited Func4).If you’d like to know exactly how to implement a stack, you can take a look at Dani Vainstein’s excellent article on the matter – here. 

A Naive Approach to Updating the Call-Chain Data on the RunWell, after we got our own storing mechanism for the call-chain data, we better start recording it. The naive approach would be to simply perform an add command once we enter a function, and a removecommand every time we exit it. So it should look something like this (assuming our data is stored in an oChainData stack storage, with add and remove commands):Function Func1 'Add to call chain oChainData.Add "Func1" '[... ' Function body ' ...] 'Remove from stack oChainData.RemoveEnd Function

Notice that we don’t have to specify the function name for our Remove command, as the stack mechanism always know to remove the top level item.

 

Problems with the Naive ApproachThat is all fine and well, but usually our code is more complex than this – usually our functions have multiple exit gates, as shown in the following example:

Page 41: Advanced QTP

Function Func1 'Add to call chain oChainData.Add "Func1" 'Code... 'If structures etc... oChainData.Remove Exit Function 'More Code... 'If structures etc... oChainData.Remove Exit Function 'More Code... 'If structures etc... oChainData.Remove Exit Function 'Now the regular exit gate: oChainData.RemoveEnd Function

When our function has multiple exit gates, it is just a matter of time before we forget to update the call-chain, and that would just make our whole stack misleading and useless.

And even if our code is perfect (and it never is), we still run the risk of a mid-function error that will throw us out, before letting us update the call-chain data. But such errors are exactly our motivation to be willing to put an effort in maintaining a call-chain mechanism in the first place!

 

A More Robust SolutionWe have seen that our naive solution fails at some basic everyday cases, but perhaps we can overcome this with more complex scripting.

What these examples show is that we need an automatic component that will “listen” to our function, and tell us (or rather the call-chain) when we enter and exit it. Implementing the whole package (monitor both entering and exiting a function) might be hard, but we can do the exit part quite easily.In order to achieve that, we could use the termination event of an object. When we write a class, anything we write within the Class_Terminate sub of the class will be executed when the class object is destroyed. So, for example:

Class KillSwitch Sub Class_Terminate Msgbox "Object terminated" End Sub

Page 42: Advanced QTP

End ClassSet oKill = New KillSwitchSet oKill = Nothing'Now the message box will appear

As you can see, the object “knows” when it is destroyed. We can use this to our advantage – instead of popping up a message box, let us make our object update the call-chain. Since the object will be destroyed whenever we exit the function (either purposefully, or due to an error), we don’t have to worry about “forgetting” to update the call-chain. It could look something like this:

'The blueprint for the updaterClass ChainUpdater Sub Class_Terminate oChainData.Remove End SubEnd ClassFunction Func1 Dim oUpdater 'This will hold the exit kill-switch oChainData.Add "Func1" 'Update the chain-data Set oUpdater = New ChainUpdater 'Create an instance of the kill switch 'Now, no matter when we exit the function, the kill switch will be activated 'And the chain-data will be updatedEnd Function

SummaryThis article introduced the concept of a call-chain and provided substantial reasons for its centrality in the automation development effort. Although the article did not provide a detailed example of a call-chain, I hope that it has successfully outlined how to build the main components that are required in order to implement such a mechanism.

The main concept explained how we can keep track of the chain-data. We have seen that the naive approach (manually update on entering and exiting a context) is impractical. We examined an alternative approach by which call-chain update is achieved automatically through an object’s kill-switch mechanism.

As aforementioned, SOLMAR Knowledge Networks has developed an automation framework with a built-in call-chain mechanism. While it is much more detailed and effective, it builds upon the same basic principles demonstrated in this article.

As a closing word, let me encourage you to share your thoughts and comments about the call-chain concept, about this article, and if you happen to dare and design, to share with the community your own call-chain mechanism!

Page 43: Advanced QTP

Recommended