Posts Tagged ‘QTP-8.2’

Comparison of QuickTest Pro 9.0 vs QTP 8.2

Monday, March 27th, 2006

How do you measure the overall quality of a product? One way is to take the spec, take the final product, and compare the two feature by feature. Well, QTP 9 went GA today, and we’ve pored over it so thoroughly you’d think we’ve had it for months. First I’ll present the summary, then over the next few days we will dissect the minutae of what they did, how well they did it, and whether or not it’s going to make your lives easier. Here’s a bullet-by-bullet summary of how well they did against their own press release:

  • Open and edit multiple Object Repositories - yes, the fact is that you can do this, but it’s not always easy, and there were a lot of missed opportunities to go from good to great
  • Multiple Object Repositories per test asset - I hate the term test asset. I think in this case they mean Action, because as far as I can tell, there is no other “thing” you can associate an OR with, and in version 9 you can associate multiple ORs with any given action. There are problems with this, but I’ll get to those later
  • Easy (Object Repository) conversion to/from XML - Yep, for what it’s worth, the conversion is easy. It is very very very slow, and the schema is complicated, but both of those are pretty understandable.
  • Easily copy/move objects between Object Repositories - Yes, you can even drag/drop objects from one repository to another, but maybe more importantly, you can reparent an object… this is super-duper cool, because you can now take all the objects under “Frame_4″ and “Frame_37″ and consolidate them under “Frame”. There are some HUGE problems with this feature that will probably be made easier in future releases, but it’s still possible and nice when you just have to do one or two objects
  • Manage functions and keywords centrally - I don’t know what this means, outside from the ability to view/edit functions from within the main IDE. If that’s the case, they’re kinda cheating in making this a separate bullet point from the “Step Into…” point below. If that’s not the case, I have no idea what this means and shall declare it an example of “silly marketingspeak”
  • New user interface - sure, OK. It’s not like the difference between vi and Visual Studio .NET, and they haven’t provided an Eclipse plugin yet (note to self: write Eclipse plugin for QTP), but I guess they’ve made some changes
  • Missing resources panel - This is très cool, but not nearly as cool as the way they make it sound
  • Comment and uncomment multiple lines in Expert View - One of those little nice things they’ve done to provide features we’re used to in every other programmer IDE on the market
  • Indent and un-indent multiple lines in Expert View - ibid
  • Pass parameters between Actions - I have no idea what this means, since you could do this before. Maybe they didn’t broadcast that you could do it before. Whatever
  • Multiple Document Interface for function libraries - I guess this means you can open more than one function lib at a time. That’s yet another restatement of the “Manage Centrally” nonsense from the earlier bullet. But I’m guessing they list it three separate times because it’s simply the single best feature of version 9. It’s changed the way we compose our tests, which is funny, because it essentially means that we use Mercury’s scaffolding a lot less because they’ve made it easier for us to write our own
  • Step Into Function Definition Code While Debugging the Test - Yes, THIS RULES!
  • Enhanced IntelliSense Support - the main thing I notice here is that your user-defined RegisterUserFunc methods now appear in the code. Maybe that was there before, but I don’t know. I could never make it work

Strictness in QuickTest (9.0 and before), Part II

Thursday, March 23rd, 2006

In our QTP framework, “strictness” has two dimensions: Test Level and Test Weight. Test Level refers to how loose the test is allowed to be when, say, selecting specific values from a WebList or WebRadioGroup. Test Weight refers to how important a given test is, and whether or not it will be run in the current cycle.

When we run tests, we run them at a default Test Level of 0, which is to say, “when you encounter a WebList or other input type, if the value I have specified does not exist, or only exists partially, just select a valid response and move on.”

The Test Weight is 200 (out of a possible 1000). The lower the Test Weight, the more important the test. When you run a 200-weight suite, you are essentially running a smoke test, where you want to run the least number of tests to validate that the system is up and running. This is what we run every time Cruise Control deploys a new site. A suite level of 800 or 1000 tells it to run every stinkin’ test we’ve written, and it takes much longer. That’s what we run at 2 am when nobody is here.

Let’s say I’m writing a QTP test where I create 15 new user accounts (each with different access features), log in as each, then perform existence checks on objects (buttons, checkboxes, etc) that should be enabled/disabled according to the access features. Given the number of different roles, features, and grants in our system, there can be over 800 permutations. Further, since we have between 4 and 8 hosted customer sites, this number mushrooms. As we host more customers, we want to scale our tests to be able to handle any number of customers, and we don’t want to have to change to code to do so.

Even further, we want to design the code so that we can re-use all these actions (User creation, logging in, access checking, etc.) in other scripts where we don’t want to run any checks beyond the most basic “did it work?” functionality.

Test Weight and Test Level are Environment variables, set in XML files, and altered at run-time to fit our needs (via an external utility). Each action has its own internal Test Weight, which is compared at run-time to Environment variable. If this internal Test Weight is greater than the weight of the suite being run, the action will short circuit and return to immediately without reporting anything or executing. If the internal Test Weight is less than or equal to the Environment variable, the test will execute.

Test Level is a lot more low-level. It is checked on a function-by-function basis, and the function in question always executes. The difference is, the Test Level will tell the function how to execute, then how to report its progress. An example would be (as above) a WebList, where you specify that “abc” be selected. If Test Level is 0 and “abc” doesn’t exist in the list, it will take the first non-empty string in the list and just select it without looking deeper into the matter. The idea is that if I’m testing a site in a different language, or a customer-deployed site that contains completely different information, the test will still work just fine.

Having these two dimensions on our tests ensures that we can run exactly the same scripts on multiple databases with varying degrees of thoroughness as the situation dictates.

Next, I’ll delve a little deeper into the different Test Levels we use, then I’ll post the code for this WebList.SmartSelect method, and how it implements the concept.

QuickTest Pro (9.0 or 8.2) method: WebRadioGroup.SelectByText

Tuesday, March 21st, 2006

We've had to solve this problem quite a bit, so Will finally turned it into an object method.

Here's the problem:

Milk

Butter

Cheese

You want to be able to say "Select the 'Butter' radio button", but the enterprising and oh-so-efficient programmer has made it so that each option has a value that corresponds to some GUID (e.g. "AFED-0839-02E8A5-0923CDB"), and "Option A" is nowhere to be found anywhere in the object's properties.

The method below allows you to send the text you wish to select, and the radio group will know exactly which button to select.

Select a Radio Button based on the adjacent descriptive text
- The WebRadioGroup to use
- The text to look for
- optional: The sWhere statement for getAdjacentText. Default is afterEnd
- optional: The Index number of the matched button to select. 0 selects first match, 1 selects second. Default is 0

Visual Basic:
  1. Public Function WebRadioGroupSelectByText ( ByRef oRadGroup, sSelectText, _
  2.                                                               sWhereOpt, sIndexOpt )
  3.     If isNull(sWhereOpt) Then
  4.         sWhereOpt = "afterEnd"
  5.     End If
  6.  
  7.     If isNull(sIndexOpt) Then
  8.         sIndexOpt = 0
  9.     End If
  10.  
  11.     sMatches = Array()
  12.  
  13.     Set oParent = oRadGroup.GetTOProperty("parent")
  14.     radName = oRadGroup.GetROProperty("name")
  15.     Set oRadioButtons = oParent.Object.GetElementsByName( radName )
  16.  
  17.     For each oElement in oRadioButtons
  18.         sName = oElement.getAdjacentText("afterEnd")
  19.         If sSelectText = sName Then
  20.             ArrayPush sMatches, oElement.Value
  21.         End If
  22.     Next
  23.  
  24.     If Ubound(sMatches) <0 Then
  25.         ' TODO: report error...item doesn't exist in list.
  26.         Exit Function
  27.     End If
  28.  
  29.     If Ubound(sMatches) <sIndexOpt Then
  30.         sIndexOpt = Ubound(sMatches)
  31.         ' TODO: report warning...index doesn't exist.  selecting last match
  32.     End If
  33.  
  34.     sSelectValue = sMatches(sIndexOpt)
  35.     oRadGroup.Select sSelectValue
  36.    
  37. End Function
  38. RegisterUserFunc "WebRadioGroup", "SelectByText", "WebRadioGroupSelectByText"

Now, to select the radio button I want, I just to this:

Visual Basic:
  1. Browser("B").Page("P").WebRadioGroup("All Options").SelectByText "Option B"

Note that this performs an exact string match. The next step is to create our WebRadioGroup.SmartSelect function, which would be much more flexible, and would allow for partial matches of either adjascent text or the traditional button values. It would also allow for pattern matching via regular expressions. We'll post that one later.

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: , , ,