Login   /   Register

Optional Parameters

Rate this article
     3 votes, average: 2.33 out of 53 votes, average: 2.33 out of 53 votes, average: 2.33 out of 53 votes, average: 2.33 out of 53 votes, average: 2.33 out of 5
Loading ... Loading ...
March 29th, 2008 by Yaron Assa

Sadly, QTP uses VBScript (natively at least). This means that in order to build effective QTP tests, we need to master VBScript syntax; as well as tips and tricks that could help us around its shortcomings.

The Problem

One of the bugging issues with VBScript is it doesn’t handle function growth and extensibility over time. Specifically, VBScript has no overloading / optional parameters support. This may sound technical if you haven’t had much experience with programming, so I’ll demonstrate using a simple example:

Function "Func" needed 2 in-parameters when you started coding, and now you want to modify it to receive a 3rd in-parameter. In most programming languages you could just add an optional 3rd parameter to the function, but in VBScript, there are no Optional parameters - so trying to call the extended function with just 2 parameters will result in an error. In other words, you’ll have to go through all of your scripts, and modify the function-calls to pass 3 parameters.

In a nutshell, this is what we call the Optional Parameters or Overloading problem in VBScript. There are many (many) ways to tackle it, each with its own set of issues. Since I truly believe that bad solutions teach as much as good ones, we’ll do a quick run through some solutions, bad as well as good ones.

The Trivial Solution

The most straight-forward solution – do nothing, and deal with the function-call updates as they come along. After all, only a handful of functions will be extended, so why go to all this trouble?

When you’ve only begun writing your first script, this may seem easy enough, and all the solutions I’ll cover will seem like a total overkill. However, when you’ve got a central function serving many projects and scripts, extending it can turn into quite a daunting task, which makes covering these solutions worth while. Anyway, the decision should be made on a case-by-case basis.

The Bridge Solution

We could just set up a bridge function between the old and the new:

Function Func (Param1, Param2)
            Result = NewWhatEver(Param1, Param2, "")
End Function
 
Function NewFunc (Param1, Param2, Param3)
  'Code goes here
End Function

This way, all the pervious function calls will still work, and from now on, whenever we actually need the 3rd parameter extension, we’ll just call NewFunc.

Of course this solution can only be used as a temporary patch for the next few runs, and never as a permanent fix. The code will get more confusing as the functions rapidly multiply, and before you know it you’ll be stuck with functions like ReallyExtraNewFunc. In the bottom line, this just wont do.

The Array Master Parameter Solution

(Shown first at 4GuysFromRolla)

This solution replaces the function’s parameters with a single array parameter, which can hold many sub-values. This way, whenever a function is extended, the number of parameters doesn’t change (there’s only one), but rather the internal data structure (which doesn’t affect the old function-calls):

'Replaced function’s  In parameters are: Status, Header, Content
Function OldFunc (MasterParam)
 Reporter.Report MasterParam(0), MasterParam(1), MasterParam(2)
End Function
 
'Replacing function - In parameters are : Status, Header, Content, File-Name
Function Func (MasterParam) ‘MasterParam is an array
 Reporter.Report MasterParam(0), MasterParam(1), MasterParam(2)
 If uBound(MasterParam) > 2 then Desktop.CaptureBitmap(MasterParam(3))
End Function

As you can see, all the previous function calls will still work (we wrapped using the new parameter with an IF statement), and further extensions are easily applicable. However, we ended up with some messy code – it’s practically unreadable, and debugging it will be a nightmare. Function parameters come with intellisense in QTP 9+, which at least tells you how many parameters the function is expecting, and their names. With MasterParam array, you’re just guessing everything from the number of parameters, to their expected value type, and order.

What’s happening in the function body is even worse; we’re down to arbitrary "unnamed" variable reference, which make it way, WAY hard to detect bugs at design time. For example, both of these options make perfect sense at design time, but the latter will produce a run-time error. This kind of arbitrary variable reference is a big text-book no-no.

1. Reporter.Report MasterParam(0), MasterParam(1), MasterParam(2)

2. Reporter.Report MasterParam(2), MasterParam(1), MasterParam(0)

So, to sum-up, this solution shows some promise, but requires some further tweaking before we could consider using it.

The Array Master Parameter Solution – The Middle Path?

We could meet at the middle. We’ll continue to write all our functions with regular parameters, but we’ll put an extra parameter – ParamArray, which will be passed blank at first, but could carry in it additional parameters if needed. This looks a lot like the original solution, but now most of out parameters are named, and the code is (somewhat) more readable:

Function Func (iStatus, sHeader, sContent, MasterParam) ‘MasterParam is an array
  Reporter.Report iStatuc, sHeader, sContent
  '8192 signifies an array variable, which means MasterParam isn’t empty
  If VarType(MasterParam) = 8192 then Desktop.CaptureBitmap(MasterParam(0))
End Function

This may prove useful for the first change or two, but once the patches pile up this method becomes even worse than the original solution. Now the parameters structure is even more arbitrary, and it becomes very hard to know exactly where the extra parameter is needed, and when we should pass it blank.

The Array Master Parameter Solution – Named Data Structure

This is the best solution I’ve found (Shown first at The Software Inquisition, though I’ve made some minor adjustments). The focus is solving the unnamed variable reference problem by using a different data structure than an array. One data structure which has a reference-by-name feature is a Scripting Dictionary object, and we’ll base our solution on it. This is the closest thing near a collection in VBScript, so it’s very useful.

First, to demonstrate the syntax for working with a scripting dictionary (More is available in QTP help -> dictionary object):

Dim oDictionary 
'Create Dictionary
Set oDictionary = CreateObject("Scripting.Dictionary")
 
'Add Value
oDictionary.Add "Key", "Value"
 
'Use Value
Msgbox oDictionary("Key") ‘Spits out "Value"
 
'Change value
oDictionary("Key") = "New Value"
 
'Check existence and value removable
If oDictionary.Exist("Key") Then oDictionary.Remove("Key")

Now, one of the things that make this object extra cool and useful (though this is not relevant for our purpose today), is that the value can be ANYTHING – string, integer, another object, etc (though not an array). This means that we can create nested dictionaries:

Dim oDictionary           'Employees collection
Dim oSubDictionary   'Employee record
Dim i
 
Set oDictionary = CreateObject("Scripting.Dictionary")
Set oSubDictionary = CreateObject("Scripting.Dictionary")
 
oSubDictionary.Add "First Name", "Yaron"
oSubDictionary.Add "Last Name", "Yaron"
oSubDictionary.Add "Age", 24
oSubDictionary.Add "Gender", "Male"
oDictionary.Add oSubDictionary("First Name"), oSubDictionary
 
'Now we can do quite interesting things. E.g.: named access to employee data
Msgbox oDictionary("Yaron")("Age")

I guess what I’m trying to say is that this is an extremely useful object, and you should read more about it. But, back to our business: with a dictionary object, we can have named variable, the same way we had named employees. This can be demonstrated by changing our array MasterParam example:

Dim oParam 
Set oParam = CreateObject("Scripting.Dictionary")
 
oParam.Add "Status", 2
oParam.Add "Header", "This is a header"
oParam.Add "Content", "This is the content"
oParam.Add "Capture Path ", "c:\picture.jpg"
 
Call Func (oParam)
 
Function Func (MasterParam)
 Reporter.Report MasterParam("Status"), MasterParam("Header"), MasterParam("Content")
 If MasterParam.Exist("Capture Path") then Desktop.CaptureBitmap(MasterParam ("Capture Path"))
End Function

Well, this is starting to look better. However, we’ve paid a heavy price for our named variable access – it’s become mighty annoying to build the Master Parameter before calling the function. There are several ways to deal with this, as shown at The Software Inquisition. Personally I love working with coded-strings (i.e. a string composed of data separated by special chars), so the method I’ll demonstrate will be based on that. Experiment with different solutions till you find the one that best fits your coding style.

The concept is to build an auxiliary function that will parse our parameter definitions into our MasterParam dictionary. This way we can fluidly write our code, while the parser deals with breaking our variable data and building a MasterParam object.

'The data is structured as VarName1>VarValue1| VarName2>VarValue2|
Function ParamParser(sData)
  Dim oResult
  Dim arrData
  Dim arrInnerData
  Dim i
 
  Set oResult = CreateObject("Scripting.Dictionary")
  arrData = Split(sData, "|") ‘Each cell holds Name>Value
  For I = 0 to uBound(arrData)
    arrInnerData = Split(arrData(i), ">")
    oResult.Add arrInnerData(0), arrInnerData(1)
  Next
 
  Set ParamParser = oResult
End Function
 
'We’ll modify our example function:
 
Function Func (sParamData)
  Dim MasterParam
  Set MasterParam = ParamParser(sParamData)
  Reporter.Report MasterParam("Status"), MasterParam("Header"), MasterParam("Content")
 
  If MasterParam.Exist("Capture Path") then Desktop.CaptureBitmap(MasterParam ("Capture Path"))
End Function
 
'And now when we call a function we can just write
 
Call Func("Status>2|Header>This is a header|Content>This is the content|Capture Path>c:\picture.jpg")

Notice that the order of the parameters makes no difference. This method makes sending parameters similar to web programming, where strings are manipulated to declare variable names and values. We still don’t know how many parameters the function needs, but tracing these errors is very easy once the variables are properly named.

As you understand, there can be gazzilion different ways for structuring the data (strings, arrays, other collections, objects etc.), parsing it, and using it, so the specifics is really up to you and your personal style.

The Array Master Parameter Solution – Named Data Structure – Default Values

There is one last (promise) tweak to this method, which deals with default values we may want to assign our optional parameters. As well as previous solutions, this method was first presented at The Software Inquisition, and I’ve only slightly modified it.

In this tweak, our ParamParser accepts two parameters (see? extending functions is something that actually can happen J). One is the actual parameters data, and the other is a set of default values for the optional parameters. The parser builds the parameters object, and merges it with the untapped optional parameters.

Function ParamParser(sData, sDefaults)
 Dim oResult
 Dim arrData
 Dim arrInnerData
 Dim i
 
 Set oResult = CreateObject("Scripting.Dictionary")
 
 arrData = Split(sData, "|") ‘Each cell holds Name>Value
 
 For I = 0 to uBound(arrData)
   arrInnerData = Split(arrData(i), ">")
   oResult.Add arrInnerData(0), arrInnerData(1)
 Next
 
 If sDefaults <> "" Then
 
  arrData = Split(sDefaults, "|") ‘Each cell holds Name>Value
 
  'Check if the default value is unused
 
   For I = 0 to uBound(arrData)
 
     arrInnerData = Split(arrData(i), ">")
 
     if not oResult.Exist(arrInnerData(0)) Then _
      oResult.Add arrInnerData(0), arrInnerData(1)
 
    Next
  End if
  Set ParamParser = oResult
End Function

And now each function which calls the parser can specify its own set of default values to complements the user specified ones:

Function Func (sParamData)
 Dim MasterParam
 Dim sDefaults
 
 sDefaults = "Capture Path>c:\qtplog\capture.jpg"
 
 Set MasterParam = ParamParser(sParamData, sDefaults)
 Reporter.Report MasterParam("Status"), MasterParam("Header"), MasterParam("Content")
 Desktop.CaptureBitmap(MasterParam ("Capture Path")) ‘We no longer need the IF protection
End Function
 
'Now even if we do:
Call Func("Status>2|Header>This is a header|Content>This is the content ")
 
'The function will capture the bitmap to the default location

Posted in Code Techniques

3 Responses to “Optional Parameters”

  1. Introduction to Code Design | BETA Says:

    [+]

    [...] try, VBScript forces us to changes our code when the function’s signature (parameters) change - more on that here. But oth... ...

  2. eduardge Says:

    [+]

    Very good and useful tip. When I used it in my code I have encountered with few little mistakes in Function ParamParser(sData, s... ...

  3. An improved dictionary object | AdvancedQTP Says:

    [-]

    […] of using it as a reserved global dictionary, a parameter storage for generic functions (here and here), and […]

Leave a Reply

You must be logged in to post a comment.

This article was viewed 1678 times