Posts Tagged ‘featured’

Classes, Objects, and QuickTest Pro 9

Wednesday, September 20th, 2006

VBScript is considered to be "object-based", meaning you can use classes, and those classes can have methods and members, but you can't get nearly the kind of re-use and extensibility you could out of a real OOP language. You don't get inheritance, you don't get polymorphism. In short, you get encapsulation, and that's about it. To me that's pretty important, the abilty to wrap up a bunch of common functions and attributes into a single entity called "an object" and throw it around kinda like you would in Java or Ruby. It allows me to create (instantiate) an object in line 10, then count on it being persisted three levels deep and 200 lines later, and never need to look inside the box to find out how it works.

I'll give some code first.

I'm using VBScript classes right now to model our AUT and make the code prettier. For example, if I want to create a user, it looks like this:

Visual Basic:
  1. LoginProfile "ADMIN"
  2. Set user = NewUser()
  3. user.create(“marcus”, “password”)
  4. user.addRole(“Administrator”)

This snippet opens a browser, logs in as an administrator, creates a new User object (basically a variable that holds attributes about a user), then uses that data (if I had supplied any) to actually create user from within the GUI. After the user is created and declared "valid", I call the "addRole()" method off of the same user. The User object knows how to fill out all the form fields. It knows how to add a role through the security interface. How? That's the beauty: I don't have to know. Someone else can develop the library of objects and maintain it, and all I have to know how to do is invoke it.

I know, I know, it's the same with functions. Yes, it is. But here they're all wrapped up together. Consider this:

Visual Basic:
  1. Set userGuest = NewUser()
  2. userGuest.create(“guest”, “password”)
  3. userGuest.addRole("Guest")

Later, if I want to know the name of that user, I can just ask the object:

Visual Basic:
  1. sName = userGuest.FirstName

Rather than have to code up a function to go and look it up somewhere. It's the persistance of data between states that I'm most interested in. The lack of inheritance and polymorphism is bad, but the most important problems are solved.

Here's the problem: in QTP, you don't just have the limitations of VBScript, you also have some severe limitations built in to (or rather "excluded from") QTP.

The class declaration (in a file called User.vbs) looks like this:

Visual Basic:
  1. Class User
  2.     Private sUserID
  3.     Public Function Init ( aOptions )
  4.         'do some proprietary stuff
  5.     End Function
  6.     Public Function create ( sUserName, sPassword )
  7.         'do some proprietary stuff
  8.     End Function
  9. End Class
  10.  
  11. Public Function NewTigerUser ( aOptions )
  12.     Set NewTigerUser = new User
  13.     NewTigerUser.Init ( aOptions )
  14. End Function

The first limitation:
That last function is there because QTP doesn't have the ability to use classes from external vbs files within the function libraries. You must give the test script access to the above function, which has access to the class, in order for your test script to be able to "see" the class.

The second limitation:
There's a bug in QTP where you can't, no matter what you do, use the debugger to step through class functions. That was almost a showstopper for me when doing this, before I learned just how few of me teammates use the debugger. When I found out I was the only one I decided that for the greater good, OO was a good direction to go in, if for no other reason than to make the code seem more familiar to us non-VBScript types.

So, in short, it's possible for us to get what we need to when we confront OO concepts, but we're severely limited by VBScript as well as Mercury. I'll submit defects on both of these issues, but I'm not sure whether I'd be optimistic about my chances. We seem to be the only ones trying to do this. Let me know if you know different!

Actions vs Functions in QTP 9.0

Monday, April 3rd, 2006

When deciding whether a piece of code should go in a reusable action or a function, we usually favored functions in QTP 8.2. When Mercury introduced QuickTest 9, they made the argument for functions even more compelling with the Multi Document Interface. Now we can open as many function libraries as we want, but we are still limited to one test. So, if you want to edit a reusable action in another test, you have to close your current test, open the second test, make your changes, save and close the second test then reopen your original test.

In addition to that new argument for functions in QTP 9, the same old arguments from 8.2 still apply. I'll sum up a few of the key differences between reusable actions and functions here.

Passing Data

Actions - can only accept primative data types as parameters (strings, integers, etc)

Functions - can accept arrays, dictionary objects and test objects (i.e. Pages, Frames, WebRadioGroups, etc.)

Resource Usage

Actions - For each action, there’s a folder, three separate vbs files, a local Object Repository, a subfolder containing snapshots, an Excel spreadsheet, and a few seconds of load time.

Functions - There’s the code contained in the Function, and that’s all

API

Actions - You cannot insert calls to Existing Actions through the QTP API, you can only do it through the interface

Functions - You can attach Function Libraries to Test Scripts through the QTP API

Return Values

Actions - Return values are difficult to understand and read in the syntax

Functions - Return values work like they do in other languages (i.e. as lvalues)

UPDATE:

In comments, Jugular Bean points out a significant downside to functions that I neglected above. You cannot change the number of parameters that a function accepts without affecting all calls to that function. That is a problem that used to drive me crazy. It seems like every time I put a new function in a library, the next test I write needs that function with one extra option. Now that I use the getopts function to define the parameters in my functions, I can easily add new optional parameters without messing up other tests that already call the function.

Named Optional Arguments in VBScript

Thursday, March 16th, 2006
QuickTest was my introduction to VBScript. I think and dream in Perl, but I am coming around to a point where I can enjoy coding in VBScript. One of my biggest VBScript annoyances early on was the inability to define optional arguments for my functions.As I said, I think in Perl, so I set about trying to find a solution similar to how I pass arguments to subs in Perl. In Perl, I like to name my arguments, so I pass an array of arguments that the sub collects into a hash as named values. The syntax looks something like this.

PERL:
  1. printsentence(
  2.     'Subject'   => 'The quick brown fox',
  3.     'Predicate' => 'jumps over the lazy dog.'
  4. );
  5.  
  6. sub printsentence {
  7.     my %args = @_;
  8.     print "$args{'Subject'} $args{'Predicate'}";
  9. }

I found an article on 4guysfromrolla.com about using optional arguments in VBScript. It suggests passing an array of arguments to the function. This is a good first step, but I don't like having to remember which value is which in the array. I still wanted my named arguments. Obviously I couldn't use a hash for this, so I used VBScript's nearest data type: a dictionary object. Once I have my parameters in a dictionary object, I can call them by name using syntax like oOptions("optionname"). The question was how to easily get all my options in a dictionary object.

I came up with a function I call DictBuild (insert platypus-related humor here). It takes an array and turns it into a dictionary object. The first value in the array is a dictionary element, the second value is that element's value, third - element, fourth - value...and so on. Basically, all even numbered values are dictionary elements and the odd value after each element name is its value.

This allows me to easily build a dictionary object with all my parameters.

Visual Basic:
  1. printsentence ARRAY( _
  2.     "Subject",     "The quick brown fox", _
  3.     "Predicate",   "jumps over the lazy dog." _
  4. )
  5.  
  6. Public Function printsentence(aOptions)
  7.     Set oOptions = BuildDict(aOptions)
  8.     MsgBox oOptions("Subject") & " " & oOptions("Predicate")
  9. End Function

Now this is finally becoming something I can work with, but what about defaults for these parameters? This is where my next function comes in: GetOpts. GetOpts takes two arrays as arguments and returns a dictionary object. The first array is the default values for the function. The second array is the custom options array that was passed to the function. GetOpts starts by building a dictionary object from the defaults array. Then it overwrites the default values with the custom options array if they exist. I also wrote some discipline for myself into GetOpts. If a option isn't listed in the defaults, it won't make it into the final dictionary.

So, now my function looks like this:

Visual Basic:
  1. printsentence ARRAY( _
  2.     "Subject",    "The quick brown fox", _
  3.     "Predicate""jumps over the lazy dog" _
  4. )
  5.  
  6. Public Function printsentence(aOptions)
  7.     Set oOptions = GetOpts( ARRAY( _
  8.         "Subject",     "Coke", _
  9.         "Predicate",   "is it", _
  10.         "Punctuation", "!" _
  11.     ), aOptions)
  12.  
  13.     MsgBox oOptions("Subject") & " " & oOptions("Predicate") & _
  14.         oOptions("Punctuation")
  15. End Function

I think that about does it for my method of using named optional arguments in VBScript functions. I should note that I only use this for optional arguments, I put the variable names directly in the function signature for required values. So one of my real function delcarations looks like

Visual Basic:
  1. Public Function WebCheckBoxSelectByText ( ByRef oCheckBox, sSelectText, aOptions )

The code for BuildDict and GetOpts is here:

Visual Basic:
  1. Public Function GetOpts(ByRef aDefaults, ByRef aCustom)
  2.     Set oDefaults = DictBuild(aDefaults)
  3.     Set oCustom = DictBuild(aCustom)
  4.     For Each vKey in oDefaults
  5.         If oCustom.Exists(vKey) Then
  6.             oDefaults.Item(vKey) = oCustom.Item(vKey)
  7.         End If
  8.     Next
  9.  
  10.     Set GetOpts = oDefaults
  11. End Function

Visual Basic:
  1. Public Function DictBuild(ByRef aArray)
  2.     bWord = True
  3.     Dim oDict
  4.     Set oDict = CreateObject("Scripting.Dictionary")
  5.     Dim sWord, sDef
  6.     If isArray(aArray) Then
  7.         For Each sString in aArray
  8.             If bWord Then
  9.                 sWord = sString
  10.                 bWord = False
  11.             Else
  12.                 sDef = sString
  13.                 oDict.Add sWord, sDef
  14.                 bWord = True
  15.                 sWord = NULL
  16.                 sDef = NULL
  17.             End If
  18.         Next
  19.     End If
  20.     set DictBuild = oDict
  21. End Function

Technorati Tags: , , ,