A lot of testers use QTP as a record-and-playback tool first and then add some logic to start building a framework around whatever their test scripts are. That’s an approach that can work but those same testers often don’t utilize one of the key strengths of QTP, which is it’s object-oriented structure.
Rather than treat it as obvious that using classes is a good idea and without giving a tutorial to object-oriented programming here, I plan to showcase some specifics to keep in mind when using a class-based approach in QTP as well as give a specific example of how this approach can be useful by showing how you can encapsulate your logic for better maintainability.
First, here’s how you can start defining a class.
1 2 3 4 5 6 7 |
Class clsEngine Private Sub Class_Initialize End Sub Private Sub Class_Terminate End Sub End Class |
Here I have two routines although in this case they are really methods. Adding the Class_Initialize and Class_Terminate methods is not something you have to do. They are essentially constructors and destructors at the class level: Initialize is called when a class object (instance) is created and Terminate is called when a class object is “destroyed.” (Here “destroyed” means that the reference variable for the instance is set to Nothing.) Both methods above are marked as Private and that’s important. The Private modifier marks a method as being available only for code stored within the class being defined. This means that you can’t directly call those methods from outside the class code.
In order to create a method which is callable from outside the class, you have to define it with the Public modifier:
1 2 3 4 |
Class clsEngine Public Function grabData() End Function End Class |
Here grabData is a method that other objects can call.
Inside the class, you can define variables as well. Like methods, variables can also be Private (which means they would only be accessible to members of the class) or Public (which means they can be used by the code from outside the class).
1 2 3 4 5 6 7 8 |
Class clsEngine Public sAppFilePath Private sAppName Public Function getAppName getAppName = sAppName End Function End Class |
In this logic, sAppName cannot be accessed directly. So I have a “getter method” (getAppName) that returns the value. (I trust anyone writing logic like this knows what a getter and setter method is so I’m not really going into that here.)
There are times where you want to protect the class data and control the means by which that data can be accessed. For example, you might want certain data to be accessible from outside the class, but in a mode where it can’t be modified. Another example might be where you want to perform some operations when the data is both read from and written to. (A good example here would be sorting an incoming array, say of stored screen names.) In order to do these things, you can wrap your data elements with a property structure. A property structure is a collection of mini-procedures, specifying what to do when the data is either read from or written to.
In the following example you can see the use of properties encapsulated within Get, Let, and Set structures. Get is used when the data is read, Let when it is written, and Set when it is written with an object reference.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Class clsEngine Public sAppFilePath Private sAppName Public Property Get AppName AppName = sAppName End Property Public Property Get AppFilePath AppFilePath = sAppFilePath End Property Public Property Let AppFilePath(sNewFilePath) sAppFilePath = sNewFilePath End Property End Class |
The modifier ‘Get’ indicates that the method will be executed when the user reads the property (AppName or AppFilePath). Those methods indicate what value will be returned to the calling method. You can see that AppName only has a Get structure, which means that the AppName can only be read, not modified. The AppFilePath, however, has a Get and Let structure and so it can be read and modified.
Remember that classes are abstract. Just as in any object-oriented language, you do have to create a class object in order to use them. A class object is an instance of the class. You can create many different instance of a class and they all will behave independently of each other:
1 2 3 4 5 6 7 8 9 10 11 12 |
Class clsEngine Public sAppFilePath Private sAppName End Class Set executeA = New clsEngine Set executeB = New clsEngine executeA.sAppFilePath = "c:\Cyborg" executeB.sAppFilePath = "c:\Cryptonomic" executeA.sAppName = "MyApp" |
The last statement above will fail because sAppName is a private property.
You can make one class instance point to another, as the last line in the following example shows:
1 2 3 4 5 6 7 8 9 10 11 12 |
Class clsEngine Public sAppFilePath Private sAppName End Class Set executeA = New clsEngine Set executeB = New clsEngine executeA.sAppFilePath = "c:\Cyborg" executeB.sAppFilePath = "c:\Cryptonomic" Set executeA.Parent = executeB |
A final note is that when a class refers to itself, you can use the reserved keyword Me. The Me keyword is only allowed from within a class instance, and it is used to refer to that instance “from the outside”. (This is equivalent to ‘this’ in languages like Java and C# or ‘self’ in languages like Ruby or Python.)
Encapsulating Logic for More Maintainability
With that introduction to QTP classes out of the way, I’m going to show you an example of some logic that’s doing a very specific task. Then I’m going to show you a way to make that less complicated. There’s actually a couple of things I’m going to show here.
- The use of description objects (instantiated from the Description class).
- The ability to get contained objects (via the ChildObjects method).
- The ability to determine properties at run-time (via the GetROProperty method).
- The use of classes to encapsulate behavior.
I’m really only going to be focusing on the last item in that list. But the other items are things you can think about as you look at the upcoming logic. So, first, here’s the starting logic:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
Dim oDesc Dim oButton Dim colObject Set oDesc = Description.Create oDesc("micclass").Value = "SwfWindow" Set colObject = Desktop.ChildObjects(oDesc) MsgBox colObject.Count For x = 0 To colObject.Count - 1 MsgBox "Name: " & colObject(x).GetROProperty("name") & Chr(13) & _ "Title: " & colObject(x).GetROProperty("title") Next Set colObject = Nothing Set oButton = Description.Create oButton("micclass").Value = "SwfButton" Set colObject = SwfWindow("name:=Home.SignOn").ChildObjects(oButton) MsgBox colObject.Count For x = 0 To colObject.Count - 1 MsgBox "Name: " & colObject(x).GetROProperty("name") & Chr(13) & _ "Title: " & colObject(x).GetROProperty("title") Next Set colObject = Nothing |
Granted that some of this is simply code to print out or clean up and so it’s more supporting code. Still, it’s hard to separate the actual logic of how things are being done from the intent of what I’m doing. In fact, it’s possible to read that and not really have any idea what it’s doing. In fact, that’s a great question: could you even tell what the above logic was doing?
Did you figure it out?
The above code logic is getting a count of certain types of objects on a given screen. But now let’s make all that just a little bit better. First, this logic can be encapsulated within a class. That class can then be instantiated. What that does is make it much easier to make the intent of the calling logic clearer. Here’s the class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
Class clsClassCount Public CountType Public Function GetClassCount(BaseObject, ClassName) Dim oDesc Dim iCount iCount = 0 If Not BaseObject.Exist(0) Then Reporter.ReportEvent micWarning, "GetClassCount", "BaseObject not found." GetClassCount = -1 Exit Function End If Set oDesc = Description.Create oDesc("micclass").Value = ClassName iCount = BaseObject.ChildObjects(oDesc).Count If Not CountType = "ALL" Then oDesc("x").Value = 0 iCount = iCount - BaseObject.ChildObjects(oDesc).Count End If Set colObject = BaseObject.ChildObjects(oDesc) For x = 0 To colObject.Count ? 1 MsgBox "Name: " & colObject(x).GetROProperty("name") Next GetClassCount = iCount End Function Public Function GetObjectCount(BaseObject, ClassName) refClassCount.CountType = "ALL" GetObjectCount = refClassCount.GetClassCount(BaseObject, ClassName) End Function Public Function GetVisibleObjectCount(BaseObject, ClassName) refClassCount.CountType = "" GetVisibleObjectCount = refClassCount.GetClassCount(BaseObject, ClassName) End Function End Class |
That’s a lot of code, right? How is that getting things better? Well, now you can instantiate that class:
1 |
Public needTo : Set needTo = New clsClassCount |
The name “needTo” might seem odd but I’m doing that because it’s going to allow me to convey the intent of what I’m doing. I “need to” do something and so I have a class reference that I refer to with naming that makes this clear. And, as promised, what that does is allow the calling logic to be much simpler:
1 2 3 4 5 |
Dim BaseObject Set BaseObject = SwfWindow("name:=Home.SignOn") MsgBox needTo.GetObjectCount(BaseObject, "SwfButton") MsgBox needTo.GetVisibleObjectCount(BaseObject, "SwfButton") |
Here, of course, I’m just using a MsgBox to print the results but in reality you’d probably have the results of the method calls going to a variable that would then be used in further processing. I think one thing you’ll notice though is that the intent is much clearer in terms of what you are doing and so where and when to use this logic is now hopefully more obvious.
Bottom-line: use classes to your advantage. Don’t just have tools like QTP generate code structures for you and use those. Instead, start building a framework. Classes are a great start on that path to creating frameworks that allow tests and logic to be intent-revealing.
Hi Jeff,
Thank you for the wonderful post related to use of classes in QTP.
I am working on creating automation framework for my project using QTP.
Hence It would be a great help if you could send some functions and sub routines created using Class.
Jeff,
It would be great if you send some classes used framework sample which will help me a lot in designing my own framework in current project
Peace !!