A Russian version of this article is available here
Abstract
This 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.
Introduction
A 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.
Test Objects and Run-Time Objects
A 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 Repository
The 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).
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.
The Object Oriented View
The Object Oriented software paradigm is based on the basic concepts of encapsulation, inheritance and 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 class methods. 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 as setCustomerId, 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.
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 Development
One 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 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:
-
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.
-
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.
-
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 Layers
I 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 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 Layer
Encapsulating Test Objects in Classes
Let 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 Login
End Class
Class MainWindow
End Class
Class CreateCustomer
End 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
'
'——————————————————————————-
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.Dictionary
End Class
Class MainWindow
Private m_htChildObjects 'As Scripting.Dictionary
End 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
'——————————————————————————-
'——————————————————————————-
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
'——————————————————————————-
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
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 Classes
The 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")
'——————————————————————————-
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:
' 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
'——————————————————————————-
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 Document
Option 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
'
'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.
Summary
This 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 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.
Posted in Descriptive Programming, Dictionary Objects, Meir Bar-Tal's Blog, Using Classes


Meir Bar-Tal




December 21st, 2008 at 5:24 pm
GUD one
http://funandknowledge.blogspot.com/2008/03/qt.html
December 23rd, 2008 at 8:26 am
Thanks.This is very useful & informative article.I am very much interested in OOPS concepts implementation.This article guides me in that way.
December 23rd, 2008 at 10:39 am
Good. This article resembled me very much this approach:
http://www.thebraidytester.com/stack.html
Regarding GUI layer we do use very similar approach. However, the most interesting is they way you implemented Verification Manager and Data Manager :).
So, looking forward to see next articles on MySystem framework. Thanks for a fine articles!
December 23rd, 2008 at 11:31 am
Thanks for your comment and the link (very interesting stuff).
It looks like the experts’ views converge to a widely agreed view of the best practices.
Best regards,
Meir
December 26th, 2008 at 5:32 am
I just want to have a try on our project with this new method. Thanks a lot for what you did.
December 31st, 2008 at 4:48 pm
Its really helpful for me to implement in next upcoming project.
Public Function IsContextLoaded(ByRef htContext),
we need to assign IsContextLoaded=True, only then it return TRUE / FALSE.Ortherwise it always return false.
If its wrong please correct me.
regards,
BB
January 7th, 2009 at 1:02 am
how do you pass a unique username and password on each iteration with this method?
January 9th, 2009 at 6:39 am
This ia an excellent article indeed and it would be an initiation to redefine the automation testing. And i do agree with the author that Automation is as good as software development.
January 11th, 2009 at 7:29 am
The article is really good one! Thanks for posting it.
I tried to implement it but I am getting a run time error
The function init() has child objects added to scripting dictionary as mentioned below…
Public Function Init()
ChildObjects = CreateObject(”Scripting.Dictionary”)
With ChildObjects
.Add “Window”, Dialog(”text:=Login”,”nativeclass:=#32770″)
.Add “AgentName”,ChildObjects(”Window”).WinEdit(”window id:=65535″,”text:=Agent Name:”,”nativeclass:=Static”)
.Add “Password”,ChildObjects(”Window”).WinEdit(”window id:=65535″,”text:=Password:”,”nativeclass:=Static”)
.Add “OK”,ChildObjects(”Window”).WinButton(”text:=OK”,”nativeclass:=Button”)
End with
Init = IsContextLoaded(ChildObjects)
End Function
I get following error message while the “AgentName”,”Password” and “OK” button are executed..
“Wrong Number of Arguments or Invalid property assignment:’ChildObject’ ”
Please help me out on this one.
January 11th, 2009 at 8:12 am
I apologize. My ChildObjects property refers to a HashTable object which is an extension of the Dictionary.
The error you get is because VB Script is unable to assert the type returned by the property, and it gets stuck at that line (it expects a dictionary and you give a string).
Instead of using the ChildObjects property directly after creating the dictionary, do the following: Define a local dictionary in the Init function, and then
Set localDic = ChildObjects
With localDic
‘Your code
End With
And this should work.
January 13th, 2009 at 9:24 am
Then we also need to use the LocalDic in SetUserName(), SetPassword() and submit() functions.
Public Function SetUserName()
set LocalDicObject = CreateObject (”Scripting.Dictionary”)
Set LocalDicObject = ChildObjects
LocalDicObject(”Username”).Set GlobalDictionary(”Username”)
Please comment if there is any other better approach for working with class dic object. With this approach in every function we have to use the localdic object to keep it working.
End Function
January 13th, 2009 at 9:50 am
No Karan, you misinterpreted my comment.
I said that because a regular Dictionary is used then it might be necessary to use a local dictionary in the Init function. There’s no need for it in the other functions, otherwise there would be no point in the whole technique. The point is that each such class is like an extended OR holding both its Test Objects and their respective business functions. In the other functions you can use them through the ChildObjects property as exemplified in the article.
January 14th, 2009 at 9:31 am
During the run, this is the behaviour I am seeing:-
Public Function SetUserName()
”No error with following code
set LocalDicObject = CreateObject(”Scripting.Dictionary”)
Set LocalDicObject = ChildObjects
LocalDicObject(”Username”).Set “karan”
”No error with following code
‘ items=ChildObjects.Items
‘ items(2).set “karan”
”with following line, Run Error-Wrong number of arguments or invalid property assignment ‘ChildObjects’
‘ ChildObjects(”Username”).set “karan”
End Function
January 14th, 2009 at 9:51 am
You can’t use the property like this. In my implementation I use a HashTable which is an extension of the native Dictionary and I am able to get the item as default. Please send me all your code and I’ll fix and explain to you what you’re doing wrong.
January 14th, 2009 at 11:03 am
Thanks for your guidence Meir on this I will try that way also. where is the gap why am I not able to think like this that I can’t use it with a string. Actually I tried this way to avoid the complexity of the globalDic which I will explore and use it.
January 20th, 2009 at 12:53 am
[…] “… testing automation … is, indeed, a specific kind of software development …” (Implementing a GUI Layer with Classes, Bar-Tal, 2008), though many times I have encountered QA managers who count automation as a testing […]
January 22nd, 2009 at 2:48 am
Great article, however I can’t get my implementation to work.
I have the same error as manjunath.rgl, and Karan.
Are you using a wrapper around the dictionary object.
There’s something that many of us are missing, that’s not in your code example.
January 22nd, 2009 at 2:52 am
“In my implementation I use a HashTable which is an extension of the native Dictionary and I am able to get the item as default.”
Do you mean that a HashTable is an extension of the built-in scripting dictionary?
I thought the built-in scripting dictionary is a hash table.
January 22nd, 2009 at 10:12 am
Hi ilcaffedio,
Yes, you’re correct. I am using a wrapper class called SKN_HashTable that extends the basic Dictionary functionality. Although you can adapt the concept even by using a native Dictionary, I should have taken this into account. I’ll post a short note on this later on today.
Thanks for your comment.
January 22nd, 2009 at 5:46 pm
Thanks for your reply.
I have it working with a scripting dictionary using the following code for the Login class, and the IsContextLoaded function:
Class Login
Private m_htChildObjects
Public Property Get ChildObjects()
Set ChildObjects = m_htChildObjects
End Property
Public Property Set ChildObjects(ByRef dic)
Set m_htChildObjects = dic
End Property
Public Function Init()
Set ChildObjects = CreateObject(”Scripting.Dictionary”)
With ChildObjects
.Add “Browser”, Browser(”opentitle:=Global Transport”)
.Add “Page”, ChildObjects.Item(”Browser”).Page(”title:=Your Page Title”)
.Add “Username”, ChildObjects.Item(”Page”).WebEdit(”html id:=Username”)
.Add “Password”, ChildObjects.Item(”Page”).WebEdit(”html id:=Password”)
.Add “Submit”, ChildObjects.Item(”Page”).WebButton(”html id:=Submit”)
End With
Init = IsContextLoaded(ChildObjects)
End Function
Public Function SetUsername()
ChildObjects.Item(”Username”).Set TestArgs(”username”)
End Function
Public Function SetPassword()
ChildObjects.Item(”Password”).Set TestArgs(”password”)
End Function
Public Function Submit()
ChildObjects.Item(”Submit”).Click
End Function
End Class
Public Function IsContextLoaded(ByRef htContext)
Dim allExist, ix, aItems, aKeys, strDetails, strAdditionalRemarks
allExist = True
aItems = htContext.Items
aKeys = htContext.Keys
For ix = 0 To htContext.Count-1
IsContextLoaded = aItems(ix).Exist(0)
strDetails = strDetails & vbNewLine & “Object #” & ix+1 & “: ‘” & aKeys(ix) & “‘ was”
If IsContextLoaded Then
strDetails = strDetails & “”
strAdditionalRemarks = “”
Else
strDetails = strDetails & ” not”
strAdditionalRemarks = ” Please check the object properties.”
allExist = False
End If
IsContextLoaded = IsContextLoaded And allExist
Select Case IsContextLoaded
Case True intStatus = micPass
Case False intStatus = micWarning
End Select
strDetails = strDetails & ” found.” & strAdditionalRemarks
Next
Reporter.ReportEvent intStatus, “IsContextLoaded”, strDetails
End Function
January 22nd, 2009 at 5:56 pm
I am glad that the concept is clear and that you managed to work it out with little effort.
Cheers!
January 23rd, 2009 at 12:51 am
Wow This is wonderful article. Thanks and appreciate it for sharing this beautiful work.
I tried to implement it and run, but I am getting some errors. I implemented same as the above code posted by “ilcaffedio”.
This is the error I am having
Object required:’m_htChildObjects’ in this step
Public Property Get ChildObjects()
Set ChildObjects = m_htChildObjects
End Property
My code is very same as the code above.
Your help would be appreciated… I really want to implement this.
January 23rd, 2009 at 2:31 am
Sorry for bugging with more… may be very basic questions
Private m_htChildObjects
Public Property Let ChildObjects(ByRef dic)
Set m_htChildObjects = dic
End Property
Public Property Get ChildObjects()
Set ChildObjects = m_htChildObjects
End Property
In this What does “dic” means. If it is getting the whole dictionary we created in “Init” then how does it get the dictionary we created in “init”.
Please explain…
Thanks
January 23rd, 2009 at 3:55 am
Hi, This me again… I found a bug in my code that I am not calling the Init(). So there is no dictionary. Now its working fine and there one more bug in the code posted by ilcaffedio. i.e.
Set ChildObjects = CreateObject(”Scripting.Dictionary”)
This should be
ChildObjects = CreateObject(”Scripting.Dictionary”)
There should not a set.
I still dont understand two things
1. Why shouldnt we put Set while assigning the dictionary object to “ChildObjets”
2. How the “dic” getting the dictionary object and passing it to “ChildObjects”.
Please forgive me if they are very basic questions.
Thanks
January 23rd, 2009 at 11:49 pm
1) That is correct. You see, if you have the following sample code:
Class MyClass
Private m_obj
‘… Other methods and properties
Property Let pB(ByVal obj)
Set m_obj = obj
End Property
End Class
Then:
Set MyObj = New MyClass
The following is incorrect:
Set MyObj.pB = CreateObject(”Scripting.Dictionary”)
Since it’s a Property Let.
Correct:
MyOBj.pB = CreateObject(”Scripting.Dictionary”)
In short, you don’t use a set when you use a Property Let. The Set is transparent to you. Just a matter of convenience.
2) When you call the Init method, it does the following:
ChildObjects = CreateObject(”Scripting.Dictionary”)
After which the m_htChildObjects will be set as a Dictionary.
I hope this makes it more clear.
Cheers,
Meir
January 28th, 2009 at 1:35 am
Hi Meir,
Thanks for the explanation. Now I understand. I tried to implement this and I am having a strange problem. I implemented this on an installer. Where it just have to click the “next” button. Here is the code.
Class Installer
Private m_htChildObjects
Public Property Let ChildObjects(ByRef dic)
Set m_htChildObjects = dic
End Property
Public Property Get ChildObjects()
Set ChildObjects = m_htChildObjects
End Property
Public Function LoadUI()
ChildObjects = CreateObject(”Scripting.Dictionary”)
With ChildObjects
.RemoveAll
.Add “win_installer”, Window(”regexpwndtitle:=.*Installer.*”,”regexpwndclass:=InstallShield_Win”)
.Add “dia_installer”, ChildObjects.Item(”win_installer”).Dialog(”text:=.*Installer.*”)
‘Win Buttons………………
.Add “next”, ChildObjects.Item(”dia_installer”).WinButton(”text:=.*Next.*”) ‘”window id:=1″,”text:=.*Next.*”\
.Add “next>”, ChildObjects.Item(”dia_installer”).WinButton(”text:=.*Next.*”)
End With
LoadUI=True
End Function
Public Function ClickButton(byval sItem)
ChildObjects.Item(lcase(sItem)).Click
End Function
End Class
Public Function Get_UI(byval sItem)
Dim oDic
Set oDic=CreateObject(”Scripting.Dictionary”)
With oDic
.RemoveAll
.Add “installer”, New Installer
‘.Add “guestview”, New GuestView
End With
If oDic.Item(lcase(sItem)).LoadUI() Then
‘msgbox “got the object”
Set Get_UI=oDic.Item(lcase(sItem))
Else
Set Get_UI=Nothing
End If
End Function
‘================================================== Code I am using in QTP ============================
Dim installer
Set installer=Get_UI(”Installer”)
With installer
Call .ClickButton(”Next”)
Call .ClickButton(”next”)
End with
First “Next” is working fine but when I tried to give click the ‘next’ button again its giving an error saying
Error: The statement contains one or more invalid function arguments in this like “ChildObjects.Item(lcase(sItem)).Click”
But when I give the code like this using “next>” which has the same properties but different key “next>” its working fine.
Dim installer
Set installer=Get_UI(”Installer”)
With installer
Call .ClickButton(”Next”)
Call .ClickButton(”next>”)
End with
Any Idea why its failing. This would help me a lot in the whole implementation.
Thanks
January 28th, 2009 at 7:05 am
Try to put a sync point between your 2 calls to ClickButton and share with us if this solved your problem.
January 28th, 2009 at 10:18 am
Hi Meir, Thanks for your quick response. Do you mean something like this, by putting some if condition with exist, to check for “next” button. I already checked this still no luck. Strangly Exist, GetRoproperty methods are working fine but not the click. When I put the click method 2nd time I am getting this.
Dim installer
Set installer=Get_UI(”Installer”)
With installer
Call .ClickButton(”Next”)
If .IsExist(”next”,10) Then
Call .ClickButton(”next”)
End If
End with
January 28th, 2009 at 10:49 am
Well, not exactly. By synchronization I mean to wait for the context to change/reload/be in ready state. From the error you described before, I gather that it is possible that the button is not recognized or that it is, but the dialog is busy (your script may run too fast).
Can you send me a screenshot of the error message and the full code to my email? It will be much easier for me to handle the problem this way.
February 15th, 2009 at 10:26 pm
Meir,
While I have implemented DP and classes in the past, the limiting factor for me has always been the client. However would you say an expert is needed in QTP to maintain the scripts.
From my experience, the people in testing Manual and a lot of times Automation are not programmers and would never be able to fix a framework.
So I am curious, have you met a lot of automation testers that are that have a programming background to maintain a framework this complex?
Also what do you consider a large-scale automation project?
Please don’t get me wrong I think what you come up with a good way to do the scripting, but I am just wondering about the aftermath when you leave the project.
February 16th, 2009 at 7:23 am
Hi Henry,
Thanks for your good points. I’ll begin my answer with a quote from G.B. Shaw: “The reasonable man adapts himself to the world; the unreasonable one persists in trying to adapt the world to himself. Therefore, all progress depends on the unreasonable man.”
The world of testing automation is relatively new, and is commonly (and mistakenly) taken as an integral part of the testing group activity. One of the main reasons for this misunderstanding lies in the aggressive promotion of tools with a record-replay instant test production promise. However, as you and me well know, this never succeeds as a good strategy that leads a firm to achieve good automation (see my presentation on My System here in my blog to get the points on what is - in my view - good automation).
Nevertheless, as code NEEDS to be written, it should in fact be a part of the development domain. From my experience, leaving this task to instant-made automation “experts” can lead to unreliable tests, which eventually must be replaced and hence the prior effort is in vain. Believe me - I’ve seen quite a few.
My vision is much more far reaching. Already when I worked for Amdocs back in 2003 I suggested that the automation DEVELOPMENT activity needs to be separated from the automation IMPLEMENTATION activity. This can be achieved by many kinds of frameworks, one of the most popular is the keyword-driven (though I still didn’t understand why HP insists that QTP is inherently a tool based on this framework), which enables one to let the automation DEVELOPER do the coding, and the TESTER to implement the test flow and data based on the components released by the developer. This scheme doesn’t require the tester to know how to code (in My System the tester needs to know how to edit an XML file to the most), and doesn’t require the automation developer to get too deeply with an AUT’s business logic. I think this makes things more efficient and release the bottleneck many times one can find regarding automation that prevents achieving maximum coverage.
The point made here is that the automation world should - as the slogan of our site suggests - go in the direction of having people of better technical skills to do the automation tasks. This does not mean that we suggest that people need to be replaced… but that an effort must be made to raise the technical level of already working automation people and newbies (see also our Career Tracks in the Services section - this also reflects our philosophy).
To your other question, what is a large-scale automation project? I can’t give an absolute answer to this. If you run 2 scripts then you’d say that it’s small-scale, but would you think 100 scripts be something big? You can’t say. It also depends on the complexity of the scripts. I once stumbled upon an automation developer who worked for 2-3 years on 2 “sets” of tests. It was actually 2 scripts written as bad as possible with regard to the points made in My System’s presentation mentioned above. They had thousands of lines of code, without comments, etc. When my manager asked me to maintain these due to the developer’s leaving the company, I refused because really, it would be much easier for me to write it from scratch.
The whole point of my framework is “Divide and Conquer” - use OO principles to design your code correctly, breaking it into small, simple pieces and you’ll be rewarded by reusable, scalable and easily maintainable code that strongly and reliably supports the implementation level of the testing team.
Finally, your points are legitimate and I didn’t get you wrong in the first place. The aftermath doesn’t need to occur; as I pointed out in the beginning, I may be irrational, but I’m pushing towards having real programmers in the profession. And as a final word: a framework that is really well assimilated by a firm should not depend on the SPECIFIC people that implement it. The change of concept should also yield organizational changes such as in the criteria to hire an automation professional (not a tester with QTP experience anymore, but rather a real developer with good testing logic understanding). And this is another aspect of My System: We provide training and support even after leaving the project (basically the idea is that to do good business you really never leave the project… or bring another resource whom you train and manage).
Well, this has been a lengthy and hopefully satisfactory reply.
Regards,
Meir
February 16th, 2009 at 8:22 am
Wow,
Great answer and thanks for sharing your ideas!!!
February 26th, 2009 at 11:33 am
Very interesting read!
I’ve been working in test automation for a number of years now and have systematically adopted new approaches after facing problems known to everyone working in test automation. I often find my selve struggling with the following question: what’s the whole point and added value of test automation? I see companies spending thousands of dollars thinking they can automate it all and it requires no effort/skill to automate.
Recently, I’ve become more and more interested in the object oriented approach. Furthermore, dividing the process in layers seems to be the only way to keep in control of all aspects of the automation process. However, I sometimes have problems making a distinction between the different layers, e.g. the test layer and the business layer. For instance: you have a business process to place an order on a website. Would you consider the end-to-end process part of the business layer or would you consider the login, selection of a product, checkout part of the business layer and the end-to-end process part of the test layer (driver that calls the individual business components).
I’m looking forward to your follow-up articles to see how you would tackle other challenges within test automation. For example: how to build/maintain end-to-end tests, how to integrate a generic system to handle test data (input/output), how to integrate a system to configure test settings, how to perform error handling, how to improve reporting, how to handle negative tests, etc. Regarding the test data par, I’m moving more and more towards databases instead of excel.
Cheers,
Joris
March 19th, 2009 at 6:53 am
Great article
May 8th, 2009 at 4:52 pm
Thanks Meir, for this gem!
There is a little extension to this article here: http://www.advancedqtp.com/forums/index.php/topic,3189.msg12436.html#new which will check the expected and actual values of the objects in the dictionary(IsStatusConform) and report if they match.
May 8th, 2009 at 10:01 pm
Thanks, Anshoo
I’ve seen your post and it’s a good one. The beautiful thing with such a framework is that once you lay the principles down, many expanding and complementary ideas just emerge spontaneously as you look at many problems and issues with the framework’s spectacles. Some things suddenly seem so easy…
I am very happy to be able to share my ideas with the community and see that you find them valuable. It is a great satisfaction for me.
Best Regards,
Meir
May 11th, 2009 at 4:46 pm
[…] Talalaev has put his time and effort into translating Meir Bar-Tal’s article on Implementing a GUI layer with classes into Russian. We full-heartily thank Sergey on his continued help and support to the QTP […]
May 19th, 2009 at 7:53 pm
Hi meir
Iam want to implement the framework using this layer.
But when i run the below script as a demo it gives me the error “Object required: ‘localDic.Item(…)’”
pls can u suggest me why this is happening
do_login()
Public Function do_login()
Dim intStatus, objLogin
Set objLogin = New Login
‘Set CreateLogin = objLogin
If objLogin.Init() Then
objLogin.SetUsername()
objLogin.SetPassword()
objLogin.Submit()
‘If login succeeds
intStatus = micPass
Else
intStatus = micFail
End If
do_login = intStatus
‘——————————————————————————-
End Function
Class Login
Public m_htChildObjects ‘As Scripting.Dictionary
Public Property Get ChildObjects()
Set ChildObjects = m_htChildObjects
End Property
Public Property Let ChildObjects(ByRef dic)
Set m_htChildObjects = dic
End Property
Public Function Init()
ChildObjects = CreateObject(”Scripting.Dictionary”)
Set localDic = ChildObjects
With localDic
localDic.Add “Browser”,Browser( ” Name:=Welcome: Mercury Tours ” )
localDic.Add “Page”, localDic.Item( ” Browser ” ).Page( “title:=Welcome: Mercury Tours ” )
localDic.Add “Username”, localDic.Item(”Page”).WebEdit(”name:=userName”)
localDic.Add “Password”, localDic.Item(”Page”).WebEdit(”name:=password”)
localDic.Add “Submit”, localDic.Item(”Page”).WebButton(”name:=Sign-In”)
End With
‘
Init = IsContextLoaded(ChildObjects)
End Function
Public Function IsContextLoaded(ByRef htContext)
‘
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
strDetails = strDetails & ” not”
strAdditionalRemarks = ” Please check the object properties.”
End If
strDetails = strDetails & ” found.” & strAdditionalRemarks
Next
‘—————————————————————————
Reporter.ReportEvent intStatus, “IsContextLoaded”, strDetails
‘——————————————————————————-
End Function
Public Function SetUsername()
ChildObjects(”Username”).Set mercury
End Function
Public Function SetPassword()
ChildObjects(”Password”).Set mercury
End Function
Public Function Submit()
ChildObjects(”Submit”).Click
End Function
End Class
June 15th, 2009 at 8:19 pm
Hi Sahib,
In SetUserName(), SetPassword() and Submit(): you will have to chance ChildObjects(”UserName”) to ChildObjects.Item(”UserName) and so on..
In the same function, chance mercury to “mercury”.
In IsContextLoaded, add the following line before the for..next loop:
IsContextLoaded = True
August 29th, 2009 at 2:14 am
[…] Implementing a GUI Layer of Classes by Meir Bar-Tal (AdvancedQTP, SolmarKN) […]
August 29th, 2009 at 2:15 am
[…] Implementing a GUI Layer of Classes by Meir-Bar Tal (AdvancedQTP, Solmar) […]
September 6th, 2009 at 5:09 am
[…] technique is based on Meir’s article, Implementing a GUI Layer with Classes. I would recommend you to read the article to better understand this approach. Class clsSiebObjects […]
September 18th, 2009 at 1:59 am
[…] an Object Dictionary This technique is based on Meir’s article, Implementing a GUI Layer with Classes. I would recommend you to read the article to better understand this approach. I have used a […]
December 2nd, 2009 at 5:03 am
Found the latest article relevant to this theme.Thanks.