Right now, I am almost finishing Gödel, Escher, Bach written by Douglas R. Hofstadter. Well, I'm on page 614, so I still have around 200 pages to go, but for me it feels like almost finishing. And this inspired me to a little piece of code in VBScript:
Dim a, p
Dim YmPool(8)
Do
mIP = afNe(a+3)*2
NextElem = el
If YmPool((a+2)*3) = aDig Or p = 1 Then
InTest = a
LitNuod "nametag", "Gateman"
Do Until a = t
Set NineHt1 = ProgId
a = (3*(2+a))
Loop
MyFile = Me
Let xEn = 2*(3+a)
End If
a = PI Mod (8)
Loop
myMid p, amId
Do you see the catch?
My next challenge: Write an actual snippet in VBScript this way that makes sense and that is actually usable.
June 28, 2008
June 25, 2008
QTP: IBAN validation in VBScript
IBAN stands for International Bank Account Number and is the old new toy of the banking community. Also hot in Europe because of SEPA. IBAN should make life easier, and maybe it does. For IT guys, IBAN is just another standard. And despite IT guys like standards (that is why they have so many of them), IBAN is a standard designed by the banking people making things a little more complicated.
The things you want to do with IBAN is validate it or just calculate the checksum on your own. The formula for the checksum is not very complex, but has some twists in it. For example, when dealing with Alpha characters, the A is transposed to 10, the B to 11 etc. In the IT world, we would transpose A to 65, B to 66… The things you don't want is validate them exactly right for for every country on this little planet. Maybe they want it, but definitely, you don't. And if they want it, get yourself a new toy called SOAP and connect to it through a service.
After searching the internet, I discovered that code for IBAN validation through any Visual Basic language was rare. I gathered the snippets I found useful and created my own IBAN functions.
How it works is all in the comments in the code, keeping your scripts maintainable and documented if you want to use it:
' This code was created by Bas M. Dam and first published on
' http://automated-chaos.blogspot.com
' You can use and distribute this code freely, as long as you
' keep this commentblock intact.
' RETRIEVING THE CHECKSUM
' There are two methods to get the checksum. The first is the
' one used in automated processes where there is an iban prototype.
' the checksum is replaced by zeros:
' MsgBox getIBANchecksum("LC00BANK1234567890", empty) 'returns 86
' The other way is a more user fiendly appraoch if only the separate
' segments are known like bank code or clearing house:
' MsgBox getIBANchecksum("BANK1234567890", "LC") 'returns 86
' CREATE AN IBAN NUMBER
' This is implemented in the makeIBAN() function for your convenience
' Msgbox makeIBAN("LC", "BANK", empty, "1234567890")
' returns LC86BANK1234567890
' Or just the simple implementation:
' Msgbox makeIBAN("LCBANK1234567890", empty, empty, empty)
' returns LC86BANK1234567890
' CHECK AN IBAN NUMBER
' And finally, you want to check if something is IBAN. You can
' use the getIBANchecksum function for it. If the result is 97,
' then you have a real IBAN, when it returns -1, there is something
' wrong with the IBAN and if it returns another number, the checksum
' is not correct
' Msgbox getIBANchecksum("LC86BANK1234567890", empty) 'returns 97
' Msgbox getIBANchecksum("LC68BANK1234567890", empty) 'returns 18
' Msgbox getIBANchecksum("LC68BANK1234567891", empty) 'returns 88
' Msgbox getIBANchecksum("LC86BANK123456789%", empty) 'returns -1
' To do this the simple way, you can make use of the isIBAN() function
' that simply returns True or False:
' Msgbox isIBAN("LC86BANK1234567890") 'returns True
' Msgbox isIBAN("LC68BANK1234567890") 'returns False
' Msgbox isIBAN("LC86BANK123456789%") 'returns False
' SPECIAL CHARACTERS
' You can use typographical characters as stated in the skipChars string.
' For now, the following characters can be used: space.-_,/
' These characters are often used to make an IBAN more readible, but are
' not taken into the checksum calculation. between the landcode
' and checksum, never a typographical character can be used.
' Msgbox isIBAN("LC86 BANK 1234 5678 90") 'returns True
' Msgbox isIBAN("LC86BANK1234.56.78.90") 'returns True
' Msgbox isIBAN("LC-86-BANK-1234-567890") 'returns False, there can not
'be a separation char between
'landcode and checksum.
' Msgbox isIBAN("LC*86*BANK*1234*567890") 'returns False, * is not a special char
' Function to check on an IBAN
Public Function isIBAN(sIban)
isIBAN = (getIBANchecksum(sIban, empty) = 97)
End Function
' Function to create an IBAN. Any of the arguments can be empty, as
' long as the first not empty argument starts with the landcode
Public function makeIBAN(landcode, bankcode, sortcode, accountnr)
dim realLandcode, sPurged
sPurged = mid(landcode & bankcode & sortcode & accountnr, 3)
realLandcode = left(landcode & bankcode & sortcode & accountnr, 2)
makeIBAN = realLandcode & getIBANchecksum(sPurged, realLandcode) & sPurged
End Function
' Function to get an IBAN checksum. Landcode can be empty, but then, the landcode
' must be included in the first two characters of sIban, followed by two zero's
Public Function getIBANchecksum(sIban, landcode)
Dim sLCCS 'Land Code and Check Sum
Dim sIbanMixed
Dim sIbanDigits
Dim char
Dim i
Dim skipChars
skipChars = " .-_,/"
' Marginal length check
If Len(sIban) < 5 Or Len(sIban) > 35 Then
getIBANchecksum = -1
Exit Function
End If
If landcode = empty Then
sLCCS = Left(sIban, 4) ' Extract land code and check sum
sIbanMixed = Right(sIban, Len(sIban) - 4) & UCase(sLCCS)
else
sLCCS = landcode & "00"
sIbanMixed = sIban & UCase(sLCCS)
End If
For i = 1 To Len(sIbanMixed)
char = Mid(sIbanMixed, i, 1)
'Check on digits
If IsNumeric(char) Then
sIbanDigits = sIbanDigits & char
'Check on typographical characters
elseif instr(skipChars, char) Then
'skip this character, but continue
'Check on non-uppercase other characters
elseif Asc(char) < 65 OR Asc(char) > 90 then
getIBANchecksum = -1
Exit function
'Transform characters to digits
else
sIbanDigits = sIbanDigits & (Asc(char) - 55)
End If
Next
getIBANchecksum = 98 - largeModulus(sIbanDigits, 97)
End Function
' Calculates the modulus of large integers that are actually
' strings. Also usefull for implementation in Excel VBA
' (there is a known bug in Excel and large number modulus)
Private Function largeModulus(sNumber, modulus)
Dim i, sRebuild(), j, r
j = 0
sNumber = cStr(sNumber)
For i = 1 To Len(sNumber) + 6 Step 6
ReDim Preserve sRebuild(j)
sRebuild(j) = Mid(sNumber, i, 6)
j = j + 1
Next
r = sRebuild(0) Mod modulus
For i = 0 To UBound(sRebuild) - 1
r = (r & sRebuild(i + 1)) Mod modulus
Next
largeModulus = r
End Function
The knowledge about the IBAN validation and some code tricks I retrieved from the internet, so it is my turn to to give it back to the community. The functions are also useful in Excel VBA, but not extensively tested. The isIBAN() function is great to use it in your spreadsheet itself, or use it as conditional formatting:
The things you want to do with IBAN is validate it or just calculate the checksum on your own. The formula for the checksum is not very complex, but has some twists in it. For example, when dealing with Alpha characters, the A is transposed to 10, the B to 11 etc. In the IT world, we would transpose A to 65, B to 66… The things you don't want is validate them exactly right for for every country on this little planet. Maybe they want it, but definitely, you don't. And if they want it, get yourself a new toy called SOAP and connect to it through a service.
After searching the internet, I discovered that code for IBAN validation through any Visual Basic language was rare. I gathered the snippets I found useful and created my own IBAN functions.
How it works is all in the comments in the code, keeping your scripts maintainable and documented if you want to use it:
' This code was created by Bas M. Dam and first published on
' http://automated-chaos.blogspot.com
' You can use and distribute this code freely, as long as you
' keep this commentblock intact.
' RETRIEVING THE CHECKSUM
' There are two methods to get the checksum. The first is the
' one used in automated processes where there is an iban prototype.
' the checksum is replaced by zeros:
' MsgBox getIBANchecksum("LC00BANK1234567890", empty) 'returns 86
' The other way is a more user fiendly appraoch if only the separate
' segments are known like bank code or clearing house:
' MsgBox getIBANchecksum("BANK1234567890", "LC") 'returns 86
' CREATE AN IBAN NUMBER
' This is implemented in the makeIBAN() function for your convenience
' Msgbox makeIBAN("LC", "BANK", empty, "1234567890")
' returns LC86BANK1234567890
' Or just the simple implementation:
' Msgbox makeIBAN("LCBANK1234567890", empty, empty, empty)
' returns LC86BANK1234567890
' CHECK AN IBAN NUMBER
' And finally, you want to check if something is IBAN. You can
' use the getIBANchecksum function for it. If the result is 97,
' then you have a real IBAN, when it returns -1, there is something
' wrong with the IBAN and if it returns another number, the checksum
' is not correct
' Msgbox getIBANchecksum("LC86BANK1234567890", empty) 'returns 97
' Msgbox getIBANchecksum("LC68BANK1234567890", empty) 'returns 18
' Msgbox getIBANchecksum("LC68BANK1234567891", empty) 'returns 88
' Msgbox getIBANchecksum("LC86BANK123456789%", empty) 'returns -1
' To do this the simple way, you can make use of the isIBAN() function
' that simply returns True or False:
' Msgbox isIBAN("LC86BANK1234567890") 'returns True
' Msgbox isIBAN("LC68BANK1234567890") 'returns False
' Msgbox isIBAN("LC86BANK123456789%") 'returns False
' SPECIAL CHARACTERS
' You can use typographical characters as stated in the skipChars string.
' For now, the following characters can be used: space.-_,/
' These characters are often used to make an IBAN more readible, but are
' not taken into the checksum calculation. between the landcode
' and checksum, never a typographical character can be used.
' Msgbox isIBAN("LC86 BANK 1234 5678 90") 'returns True
' Msgbox isIBAN("LC86BANK1234.56.78.90") 'returns True
' Msgbox isIBAN("LC-86-BANK-1234-567890") 'returns False, there can not
'be a separation char between
'landcode and checksum.
' Msgbox isIBAN("LC*86*BANK*1234*567890") 'returns False, * is not a special char
' Function to check on an IBAN
Public Function isIBAN(sIban)
isIBAN = (getIBANchecksum(sIban, empty) = 97)
End Function
' Function to create an IBAN. Any of the arguments can be empty, as
' long as the first not empty argument starts with the landcode
Public function makeIBAN(landcode, bankcode, sortcode, accountnr)
dim realLandcode, sPurged
sPurged = mid(landcode & bankcode & sortcode & accountnr, 3)
realLandcode = left(landcode & bankcode & sortcode & accountnr, 2)
makeIBAN = realLandcode & getIBANchecksum(sPurged, realLandcode) & sPurged
End Function
' Function to get an IBAN checksum. Landcode can be empty, but then, the landcode
' must be included in the first two characters of sIban, followed by two zero's
Public Function getIBANchecksum(sIban, landcode)
Dim sLCCS 'Land Code and Check Sum
Dim sIbanMixed
Dim sIbanDigits
Dim char
Dim i
Dim skipChars
skipChars = " .-_,/"
' Marginal length check
If Len(sIban) < 5 Or Len(sIban) > 35 Then
getIBANchecksum = -1
Exit Function
End If
If landcode = empty Then
sLCCS = Left(sIban, 4) ' Extract land code and check sum
sIbanMixed = Right(sIban, Len(sIban) - 4) & UCase(sLCCS)
else
sLCCS = landcode & "00"
sIbanMixed = sIban & UCase(sLCCS)
End If
For i = 1 To Len(sIbanMixed)
char = Mid(sIbanMixed, i, 1)
'Check on digits
If IsNumeric(char) Then
sIbanDigits = sIbanDigits & char
'Check on typographical characters
elseif instr(skipChars, char) Then
'skip this character, but continue
'Check on non-uppercase other characters
elseif Asc(char) < 65 OR Asc(char) > 90 then
getIBANchecksum = -1
Exit function
'Transform characters to digits
else
sIbanDigits = sIbanDigits & (Asc(char) - 55)
End If
Next
getIBANchecksum = 98 - largeModulus(sIbanDigits, 97)
End Function
' Calculates the modulus of large integers that are actually
' strings. Also usefull for implementation in Excel VBA
' (there is a known bug in Excel and large number modulus)
Private Function largeModulus(sNumber, modulus)
Dim i, sRebuild(), j, r
j = 0
sNumber = cStr(sNumber)
For i = 1 To Len(sNumber) + 6 Step 6
ReDim Preserve sRebuild(j)
sRebuild(j) = Mid(sNumber, i, 6)
j = j + 1
Next
r = sRebuild(0) Mod modulus
For i = 0 To UBound(sRebuild) - 1
r = (r & sRebuild(i + 1)) Mod modulus
Next
largeModulus = r
End Function
The knowledge about the IBAN validation and some code tricks I retrieved from the internet, so it is my turn to to give it back to the community. The functions are also useful in Excel VBA, but not extensively tested. The isIBAN() function is great to use it in your spreadsheet itself, or use it as conditional formatting:
June 18, 2008
Continued: Creating a Queue data type in QTP
DONG, Housekeeping message: If you have questions, additional information, an opinion or just something to share, you are very welcome to leave it in the comments. I like to get personal mail too, but as mentioned on coding horrors: on a blog, the comments are the best part. And since I just started, I still need some!
In addition to a question I received through email: The class to create a queue on the linked list I published Monday, and how to perform a count on this queue.
Public function queue()
set queue = new clsQueue
End Function
Class clsQueue
Private ll
Private Sub Class_Initialize ' Setup Initialize event.
set ll = linkedList ' Create a linked list instance
End Sub
Public function pop()
If ll.count = 0 Then exit function ' No items in the list
pop = ll.getfirst() ' Return the last item
ll.deleteFirst ' and delete it
End Function
Public sub push(element)
ll.add element ' Add an item to the list
End sub
Public function peek()
peek = ll.getfirst() ' Peek at the top item
End Function
Public property get count()
'return the amount of nodes
count = ll.count
End Property
End Class
And this is the part where inheritance and polymorphism is missing. The class looks kind of the same as the stack class, but .getlast() is replaced by .getfirst(). I wish everything in life was this simple… The count property is added, so you can now count the items in the queue. If you want to add a count property to the stack, just do the same in the clsStack.
In addition to a question I received through email: The class to create a queue on the linked list I published Monday, and how to perform a count on this queue.
Public function queue()
set queue = new clsQueue
End Function
Class clsQueue
Private ll
Private Sub Class_Initialize ' Setup Initialize event.
set ll = linkedList ' Create a linked list instance
End Sub
Public function pop()
If ll.count = 0 Then exit function ' No items in the list
pop = ll.getfirst() ' Return the last item
ll.deleteFirst ' and delete it
End Function
Public sub push(element)
ll.add element ' Add an item to the list
End sub
Public function peek()
peek = ll.getfirst() ' Peek at the top item
End Function
Public property get count()
'return the amount of nodes
count = ll.count
End Property
End Class
And this is the part where inheritance and polymorphism is missing. The class looks kind of the same as the stack class, but .getlast() is replaced by .getfirst(). I wish everything in life was this simple… The count property is added, so you can now count the items in the queue. If you want to add a count property to the stack, just do the same in the clsStack.
Labels:
abstract data types,
classes,
coding,
QTP
June 16, 2008
Abstract data types in QTP: The linked list
Normally the need for abstract datatypes in Test Automation is not very high. But you will see, just if you don’t have direct access to them, you’ll need them most. Today I had to store some data and it would be nice if I could pop and push it on some stacks. The basis for stacks (and queues) is a linked list. This was a nice Monday morning starter. Linked lists are simple, if you have the right toolset. Unfortunately, QTP and VBScript do not support pointers, so I had to be a little creative. First of all, I created a node class. Each node has a unique index number and a reference to the index numbers of its left and right neighbours. The data is conserved in a public available data element that can contain objects or variants.
' This is a setup for a linked list. This linked list is the basis for
' queues and stacks. With a little adaptation this can be transformed to
' a binary three or more complex abstract data types.
Option explicit
' Initializer for a new listnode. If you keep it private, you don't need this
' caller function
Private function listNode()
Set listNode = new clsListNode
End Function
' class for a node. The nodeIndex functions as pointerreference
Class clsListNode
Public data 'Data, can be a variable or an object (not an array)
Public prevNode
Public nextNode
Private nodeIndex 'Index reference
' I use a property get to make a defaulter
Public default property get index()
index = nodeIndex
End Property
' and a property let to set a new indexer
Public property let index(inr)
nodeIndex = inr
End Property
End class
To keep track of the nodes, I created an array called collection containing node elements. Collection() is a list of references, but is never referred directly except when we add a new node. If a new node is added, the subscript of collection is incremented to create a new unique reference. Because elements can be added or deleted randomly, do not use collection() and its subscript as a continuous or a chronological list!
With add(), we can add an element to the end of the linked list. With getlast() and getfirst() the last and first data elements on the linked list are returned and with deletelast() and deletefirst() the last and first nodes are deleted.
Count() is a helper function, returning the amount of nodes in the list.
' Make the linked list caller
Public function linkedList()
set linkedList = new clsLinkedList
End Function
' The linked list call
Class clsLinkedList
private collection() ' a single dimensional array of listNodes
Private lastItemIndex ' index of the last item
Private firstItemIndex ' index of the first item
Private indexNumber ' counter for nodes in the list. Only increment, never decrement!
Private nodeCount ' amount of nodes. Ugly but fast.
Private Sub Class_Initialize ' Setup Initialize event.
lastItemIndex = null
firstItemIndex = null
indexNumber = 0
nodeCount = 0
End Sub
Public sub add(data)
' add new listNode element to the array
ReDim preserve collection(indexNumber)
set collection(indexNumber) = listNode
With collection(indexNumber)
' Make the collection compatible for objects as well as normal variables
If isObject(data) Then
set .data = data
else
.data = data
End If
.prevNode = lastItemIndex ' The previous node of the new node is the current
' last node reference
.nextNode = null ' As it is the last item, there is no reference
' to the next item
.index() = indexNumber ' Set the index of the node to a unique number
If not isnull(.prevNode) Then ' If the newly created node has a left neighbour:
collection(.prevNode).nextNode = indexNumber ' the nextnode reference of the left
else ' neighbour is the index of the newly created
firstItemIndex = .index ' Else, this is the first node and the first node reference is the
end if ' index of the newly created node
end with
lastItemIndex = indexNumber ' As this is the last node, set the reference to this indexnumber
indexNumber = indexNumber + 1 ' make a new indexnumber for a unique reference next time
nodeCount = nodeCount + 1 ' and increment the nodecounter
End sub
Public property get getlast()
If nodeCount = 0 Then exit property ' No nodes? exit!
If isobject(collection(lastItemIndex).data) then ' Object or variant?
Set getlast = collection(lastItemIndex).data ' return object
else
getlast = collection(lastItemIndex).data ' return variant
end if
End Property
Public property get getfirst()
If nodeCount = 0 Then exit property
If isobject(collection(firstItemIndex).data) then
Set getfirst = collection(firstItemIndex).data
else
getfirst = collection(firstItemIndex).data
end if
End Property
Public sub deletelast()
If nodeCount = 0 Then exit sub 'Exit on no nodes
Dim tempLastIndex
tempLastIndex = lastItemIndex 'Make a temp for the last index number
' Check if there is a previous node
If not isnull(collection(lastItemIndex).prevNode) Then
' Set the reference for the last item to the index of the left neighbour
lastItemIndex = collection(collection(lastItemIndex).prevNode).index
' Set the reference for the next node of the left neighbour to null
collection(lastItemIndex).nextNode = null
End If
' destroy the node element
Set collection(tempLastIndex) = nothing
' decrement the node counter
nodeCount = nodeCount - 1
End Sub
Public sub deletefirst()
If nodeCount = 0 Then exit sub
Dim tempFirstIndex
tempFirstIndex = firstItemIndex
' Check if there is a next node
If not isnull(collection(firstItemIndex).nextNode) Then
' Set the reference for the first item to the index of the right neighbour
firstItemIndex = collection(collection(firstItemIndex).nextNode).index
' Set the reference for the previous node of the right neighbour to null
collection(firstItemIndex).prevNode = null
End If
Set collection(tempFirstIndex) = nothing
nodeCount = nodeCount - 1
End Sub
Public property get count()
'return the amount of nodes
count = nodeCount
End Property
end class
Some test statements to show how it works::
Dim ll, mc
Set ll = linkedList
ll.add "first"
ll.add "second"
ll.add "third"
ll.add "fourth"
ll.add "fifth"
msgbox ll.getFirst() ' >first
msgbox ll.getLast() ' >fifth
' Delete the first item
ll.deletefirst
msgbox ll.getFirst() ' >second
msgbox ll.count ' >4
' Delete the last two items
ll.deletelast
ll.deletelast
msgbox ll.getLast() ' >third
The linked list class makes it very easy to create stacks and queues. Here is an example of how to create a stack:
Public function stack()
set stack = new clsStack
End Function
Class clsStack
Private ll
Private Sub Class_Initialize ' Setup Initialize event.
set ll = linkedList ' Create a linked list instance
End Sub
Public function pop()
If ll.count = 0 Then exit function ' No items in the list
pop = ll.getlast() ' Return the last item
ll.deleteLast ' and delete it
End Function
Public sub push(element)
ll.add element ' Add an item to the list
End sub
Public function peek()
peek = ll.getlast() ' Peek at the top item
End Function
End Class
And some sample code for a little demonstration:
Dim myStack
set myStack = stack ' Set as new stack
myStack.push "item1" ' Add some items
myStack.push "item2"
myStack.push "item3"
msgbox myStack.pop() ' >item3
msgbox myStack.peek() ' >item2
msgbox myStack.pop() ' >item2
The thing that is missing for generic use is an insertbefore() and an insertafter() method on the linked list. But to implement this, you'll need a virtual reference table that maps the collection() array index to a chronologic ánd continuous index. Another way to do this is a looping mechanism where you can walk through the elements with a getnext() or getprevious() method.
For now, the stack and queue functionality is sufficient for my needs.
' This is a setup for a linked list. This linked list is the basis for
' queues and stacks. With a little adaptation this can be transformed to
' a binary three or more complex abstract data types.
Option explicit
' Initializer for a new listnode. If you keep it private, you don't need this
' caller function
Private function listNode()
Set listNode = new clsListNode
End Function
' class for a node. The nodeIndex functions as pointerreference
Class clsListNode
Public data 'Data, can be a variable or an object (not an array)
Public prevNode
Public nextNode
Private nodeIndex 'Index reference
' I use a property get to make a defaulter
Public default property get index()
index = nodeIndex
End Property
' and a property let to set a new indexer
Public property let index(inr)
nodeIndex = inr
End Property
End class
To keep track of the nodes, I created an array called collection containing node elements. Collection() is a list of references, but is never referred directly except when we add a new node. If a new node is added, the subscript of collection is incremented to create a new unique reference. Because elements can be added or deleted randomly, do not use collection() and its subscript as a continuous or a chronological list!
With add(), we can add an element to the end of the linked list. With getlast() and getfirst() the last and first data elements on the linked list are returned and with deletelast() and deletefirst() the last and first nodes are deleted.
Count() is a helper function, returning the amount of nodes in the list.
' Make the linked list caller
Public function linkedList()
set linkedList = new clsLinkedList
End Function
' The linked list call
Class clsLinkedList
private collection() ' a single dimensional array of listNodes
Private lastItemIndex ' index of the last item
Private firstItemIndex ' index of the first item
Private indexNumber ' counter for nodes in the list. Only increment, never decrement!
Private nodeCount ' amount of nodes. Ugly but fast.
Private Sub Class_Initialize ' Setup Initialize event.
lastItemIndex = null
firstItemIndex = null
indexNumber = 0
nodeCount = 0
End Sub
Public sub add(data)
' add new listNode element to the array
ReDim preserve collection(indexNumber)
set collection(indexNumber) = listNode
With collection(indexNumber)
' Make the collection compatible for objects as well as normal variables
If isObject(data) Then
set .data = data
else
.data = data
End If
.prevNode = lastItemIndex ' The previous node of the new node is the current
' last node reference
.nextNode = null ' As it is the last item, there is no reference
' to the next item
.index() = indexNumber ' Set the index of the node to a unique number
If not isnull(.prevNode) Then ' If the newly created node has a left neighbour:
collection(.prevNode).nextNode = indexNumber ' the nextnode reference of the left
else ' neighbour is the index of the newly created
firstItemIndex = .index ' Else, this is the first node and the first node reference is the
end if ' index of the newly created node
end with
lastItemIndex = indexNumber ' As this is the last node, set the reference to this indexnumber
indexNumber = indexNumber + 1 ' make a new indexnumber for a unique reference next time
nodeCount = nodeCount + 1 ' and increment the nodecounter
End sub
Public property get getlast()
If nodeCount = 0 Then exit property ' No nodes? exit!
If isobject(collection(lastItemIndex).data) then ' Object or variant?
Set getlast = collection(lastItemIndex).data ' return object
else
getlast = collection(lastItemIndex).data ' return variant
end if
End Property
Public property get getfirst()
If nodeCount = 0 Then exit property
If isobject(collection(firstItemIndex).data) then
Set getfirst = collection(firstItemIndex).data
else
getfirst = collection(firstItemIndex).data
end if
End Property
Public sub deletelast()
If nodeCount = 0 Then exit sub 'Exit on no nodes
Dim tempLastIndex
tempLastIndex = lastItemIndex 'Make a temp for the last index number
' Check if there is a previous node
If not isnull(collection(lastItemIndex).prevNode) Then
' Set the reference for the last item to the index of the left neighbour
lastItemIndex = collection(collection(lastItemIndex).prevNode).index
' Set the reference for the next node of the left neighbour to null
collection(lastItemIndex).nextNode = null
End If
' destroy the node element
Set collection(tempLastIndex) = nothing
' decrement the node counter
nodeCount = nodeCount - 1
End Sub
Public sub deletefirst()
If nodeCount = 0 Then exit sub
Dim tempFirstIndex
tempFirstIndex = firstItemIndex
' Check if there is a next node
If not isnull(collection(firstItemIndex).nextNode) Then
' Set the reference for the first item to the index of the right neighbour
firstItemIndex = collection(collection(firstItemIndex).nextNode).index
' Set the reference for the previous node of the right neighbour to null
collection(firstItemIndex).prevNode = null
End If
Set collection(tempFirstIndex) = nothing
nodeCount = nodeCount - 1
End Sub
Public property get count()
'return the amount of nodes
count = nodeCount
End Property
end class
Some test statements to show how it works::
Dim ll, mc
Set ll = linkedList
ll.add "first"
ll.add "second"
ll.add "third"
ll.add "fourth"
ll.add "fifth"
msgbox ll.getFirst() ' >first
msgbox ll.getLast() ' >fifth
' Delete the first item
ll.deletefirst
msgbox ll.getFirst() ' >second
msgbox ll.count ' >4
' Delete the last two items
ll.deletelast
ll.deletelast
msgbox ll.getLast() ' >third
The linked list class makes it very easy to create stacks and queues. Here is an example of how to create a stack:
Public function stack()
set stack = new clsStack
End Function
Class clsStack
Private ll
Private Sub Class_Initialize ' Setup Initialize event.
set ll = linkedList ' Create a linked list instance
End Sub
Public function pop()
If ll.count = 0 Then exit function ' No items in the list
pop = ll.getlast() ' Return the last item
ll.deleteLast ' and delete it
End Function
Public sub push(element)
ll.add element ' Add an item to the list
End sub
Public function peek()
peek = ll.getlast() ' Peek at the top item
End Function
End Class
And some sample code for a little demonstration:
Dim myStack
set myStack = stack ' Set as new stack
myStack.push "item1" ' Add some items
myStack.push "item2"
myStack.push "item3"
msgbox myStack.pop() ' >item3
msgbox myStack.peek() ' >item2
msgbox myStack.pop() ' >item2
The thing that is missing for generic use is an insertbefore() and an insertafter() method on the linked list. But to implement this, you'll need a virtual reference table that maps the collection() array index to a chronologic ánd continuous index. Another way to do this is a looping mechanism where you can walk through the elements with a getnext() or getprevious() method.
For now, the stack and queue functionality is sufficient for my needs.
Labels:
abstract data types,
classes,
coding,
QTP
June 12, 2008
Option Explicit and other QTP curiosities
I like variable declaration and strong type casting. It makes sense, because it makes sense in assembly. And it prevents me from making mistakes in variable names. QTP offers variable declaration with the command "option explicit". This works just the same as in VBA or VB, but... you need to be consequent. Every action script, function library or external vbs file needs an option explicit command or else it won't work. You thought you didn't need it in that tiny class file where mistakes are not possible? You thought wrong. Ignoring one option explicit, makes all other option explicits obsolete and your scripts will be interpreted without the need for variable declaration.
By the way, undeclared variable detection only works during runtime. There is no pre-compile option as in VB or VBA, so it is good old trial and error when you want to debug your script on undeclared variables.
"Randomize" has sort of the same behavior: Don't put it in a header script or such kind, but put it in the library where you use the rnd function. If you use rnd in multiple libraries, you'll have to put Randomize in each of them, otherwise you are stuck with the same 'random' numbers each time you run the script.
By the way, undeclared variable detection only works during runtime. There is no pre-compile option as in VB or VBA, so it is good old trial and error when you want to debug your script on undeclared variables.
"Randomize" has sort of the same behavior: Don't put it in a header script or such kind, but put it in the library where you use the rnd function. If you use rnd in multiple libraries, you'll have to put Randomize in each of them, otherwise you are stuck with the same 'random' numbers each time you run the script.
Labels:
code optimization,
coding,
QTP,
tricks
June 11, 2008
object.exist() in QTP needs company
It took me some time last week to figure out that the .exist() method does not work on a stand alone base. It passes a return value, and you need to do something with that value. For normal usage of .exist() this is obvious:
If QTPobject.exist Then msgbox "The object is present!"
But as a WinRunner user (did I mention that before?) I use the appearance of objects as an intrinsic synchronization method like:
btn_click("Open New Window");
win_exists("New Window", wait_normal); # synchronize 'wait_normal' seconds
set_window("New Window", 1);
...
Since I did a lot with webtesting, I didn't like to use the set_window() function before the frame was actually present, because it could cause a IE crash. That is why I used the win_exists() function.
But now in QTP. When you use .exist() with QTP objects in this way, it won't work:
QTPobject.exist
or
call QTPobject.exist
results in an 'Method is not valid for this object' error.
The only way to let it work, is passing the outcome to a dummy variable:
dummy = QTPobject.exist
This sometimes exotic behavior of QTP can be frustrating.
June 6, 2008
QTP and the curse of the interview questions
Quick Test Professional is getting popular. It wins easily from good-old-and-soon-to-be-disposed WinRunner. Because QTP is relatively new, all over the internet the knowledge bases are still building up. That is good thing. Not a good thing in my opinion is the way people are trying to get a job. Just do a search on “QTP interview question” or better “QTP interview answers” and see for yourself; there seems to be a large market for publishing answers on interview questions. And this is something that scares me.
When a project wants to start automating their tests, it is automatically a shift in the test process. Testers have to be stricter in defining and formatting their data, business analysts do have to make clear requirements and the development team has to cope with standards. None of them is a bad thing, unless you don’t want to improve your test and development process of course.
Most of the time there is no automated test available yet and the project is searching for a specialist who can design and implement the automated test. When the decision for test automation is made, the business puts lots of assets on the automated tests that have to be developed. Besides the significant costs, the assets also include the change and implementation of the architecture, test framework, risk model, the fitting on the manual tests and the test process.
And then, the interviews. The interviews with potential test automation specialists are most of the time performed by test managers, project leads and colleague testers, none of them very experienced in test automation. The most technical guy of them probably had played with the test automation software a bit, and he is the likely one to ask some technical questions.
But how can you make a difference between candidate A and candidate B if the answers to those questions can be learned by head? Or even worse, what if you only have candidates that have their knowledge from the internet? You pick the best one and you are satisfied with that, but it is like choosing between a blind carpenter and one without hands.
Right now, there are more people who like to work with WinRunner than to actually learn it. Soon enough, QTP will end up with the same statistics. That is not a good sign for the automated test business in general. Inexperienced people on projects claiming the contrary might result in failures. Project managers don’t like failures and when test automation gets associated with failure, you'll get a hard time convincing them of the opposite.
Certification is a hot topic right now. I am not a big proponent of pointlessly requiring a certificate for every tool you want to use. On the other site, it sifts the wheat from the chaff at forehand and it gives an instrument to projects, inexperienced with test automation, letting them easily select the experts from an applicant pool.
When a project wants to start automating their tests, it is automatically a shift in the test process. Testers have to be stricter in defining and formatting their data, business analysts do have to make clear requirements and the development team has to cope with standards. None of them is a bad thing, unless you don’t want to improve your test and development process of course.
Most of the time there is no automated test available yet and the project is searching for a specialist who can design and implement the automated test. When the decision for test automation is made, the business puts lots of assets on the automated tests that have to be developed. Besides the significant costs, the assets also include the change and implementation of the architecture, test framework, risk model, the fitting on the manual tests and the test process.
And then, the interviews. The interviews with potential test automation specialists are most of the time performed by test managers, project leads and colleague testers, none of them very experienced in test automation. The most technical guy of them probably had played with the test automation software a bit, and he is the likely one to ask some technical questions.
But how can you make a difference between candidate A and candidate B if the answers to those questions can be learned by head? Or even worse, what if you only have candidates that have their knowledge from the internet? You pick the best one and you are satisfied with that, but it is like choosing between a blind carpenter and one without hands.
Right now, there are more people who like to work with WinRunner than to actually learn it. Soon enough, QTP will end up with the same statistics. That is not a good sign for the automated test business in general. Inexperienced people on projects claiming the contrary might result in failures. Project managers don’t like failures and when test automation gets associated with failure, you'll get a hard time convincing them of the opposite.
Certification is a hot topic right now. I am not a big proponent of pointlessly requiring a certificate for every tool you want to use. On the other site, it sifts the wheat from the chaff at forehand and it gives an instrument to projects, inexperienced with test automation, letting them easily select the experts from an applicant pool.
Labels:
automated testing,
QTP
June 5, 2008
Real Regular Expressions in WinRunner
Automated testing without regular expressions is almost unthinkable nowadays. But there was a time, almost nobody knew what regex's were. Only a handful of developers and they were all working on Unix.
WinRunner started with support of regex's years ago, because it seemed to be handy for testers. Unfortunately they did it only with their own set of limited meta characters. You can use the . for any character, the * for zero to unlimited of the previous character, the square brackets [ ] for ranges or included characters and that was it.
Probably they thought that the full set of regular expression characters was overkill for non technical users like testers, and they could not know that regular expressions would become so regular.
I presume that for compatibility purposes Mercury never upgraded their set of regular expressions, but it is a real handicap when you are deep into the test automation.
After a bit of searching, I found this site on the internet: Misha's regex. Misha did a great job, porting C++ regular expressions to a dynamic library that can be loaded into WinRunner. Also, he provides some WinRunner functions to give you a kick start when you want to work with it.
As far as I know, this is the only ready to use solution for WinRunner to implement full regular expressions. And even better, you can now use "label_like" and "id_like" as attributes in object recognition.
The only thing I did was writing to simple functions to do a simple match:
public function re_match_simple(in orig_str, in pattern) {
auto outPos, outLen, outDetail;
return re_match(orig_str, pattern, outPos, outLen, outDetail);
}
public function re_search_simple(in orig_str, in pattern) {
auto outPos, outLen, outDetail;
if (re_search(orig_str, pattern, outPos, outLen, outDetail))
return outPos+1; # +1, otherwise 0 (=FALSE) will be returned if found on first position
return FALSE;
}
This allows me to do quick checks like
if (!re_match_simple(outDate, "^\d{1,2}-[JFMASOND][aepuco][nbrylgptvc]-(19|20)\d\d$")
myReport("Date format for '"outDate"' is not according standard: dd-Mmm-yyyy", eCHECK);
or
customerId = substr(fullText, re_search_simple(fullText, "\d{8}"), 8);
Even the test analists are working with regular expressions. (on that particular project we worked with separated roles: testautomation and testanalysis). The only thing they had to do was flag a string with an exclamation mark (!) in front and it was checked as an regular expression. The exlamation mark for obvious reasons.
June 4, 2008
QTP pitfalls for WinRunner users (Part II)
Today was another day of fiddling around with QTP. The VBScript language of QTP is still a bit new to me and I stumbled into some neatly disguised traps:
1. The .exist(n) method is in seconds
Normally I use the .exist() method for synchronization, but today I used it for checking if the right finalise page was reached or that I had to cope with an data validation error somewhere. I do not bother test objects, because at this moment I am not testing, but just doing an automated data entry project with some complex datamodels. (This is where the classes are hopping in I mentioned before).
To test my code, I started my QTP script and went for my lunch. When I got back, only one set of data was entered and the screen was synchronizing on a page. And synchronizing. And synchronizing.
After pressing the stop button and debugging my code, I found out that the wait time in exist(n) is expected in seconds, while the help file mentions milliseconds!
My script was waiting for a never appearing page for 10.000 seconds instead of 10.000 milliseconds.
Not very uncommon for the WinRunner user by the way (obj_exists() and win_exists() works almost the same), only misleading because of incorrect information the help file.
2. Location is optional in object identification
When there are multiple object in your AUT that match the same description in the object repository, QTP just picks the first one when the ordinal properties are set to 'none'.
And this property is always set to none whenever you learned the object while it was unique on the screen.
In WinRunner, you used to get the error message E_NOT_UNIQUE, but QTP is figuring it out all by itself, even with Smart Identification switched off. Something to keep in mind.
3. Eval() is not exactly eval();
Eval() in QTP is used for comparison, while Execute() is used for assignment:
a = 2
msgbox eval("a = 2") ' displays 'True'
msgbox a ' displays '2'
msgbox execute("a = 3") ' displays nothing, a is set to 3
msgbox a ' displays '3'
Keep this in mind if you used the eval() function in WinRunner for assignment of virtual variables. It won't work in QTP and you have to use execute().
4. There is no fast way to export the object repository to plain text
Unlike WinRunner, where the GUI map was just a plain text file, it is not possible to easily export (and manipulate) the object repository. It is possible though, but you'll have to do it through COM automation on the QTP application. I tried it today, but I failed miserably because I could not access the ActiveX object (probably not enough authorization on my workstation).
However, if I get it working, I will post the code on Automated Chaos.
Labels:
mercury,
object repository,
pitfalls,
QTP,
transition,
winrunner
June 3, 2008
My Top 5 Ultimate Utilities for Testing
This is my list of top 5 utilities I use for (automated) testing. I use them as helper programs in the (automated) test process, so this is not a list stating automated test tools, bug loggers etc. Every header is a link to the location where you can find the application.
5. API Viewer
View all useable functions and constants from all common Window API's and auto generate C/VB code for it.
4. Pycron
Your own cron tab under Windows, with GUI for editing events. You can run this as command line or as service. Stable, small and can run stand alone.
3. Notepad++ Portable
A notepad with many options. Simular to text editors as ultraedit except this one is free and can run stand alone (if you choose the portable version).
2. Synergy
Very useful when having multiple PCs with multiple monitors, but when you only want to use 1 keyboard/mouse combination. Mouse movement works just as you would expect from a dual/triple monitor setup, only you cannot drag your windows to the other screen (because it is a physical other machine). However the application manages to get copy pasting of text working most of the time.
1. Gadwin Printscreen
A customizable screen dump tool with one major advantage above lots of others: It can be called by the Command line. This gives it a great usage during automated testing.
Contains all features you'll expect in an ultimate tool: jpg, png, bmp and gif formats, custom filename, incremential filename, silence mode etc.
This application works stand alone (once unpacked, there is no installation needed on specific workstations, just run the executable).
Lowlevel Code Optimization in WinRunner
At some moment in your automated test project you enter the phase you are questioning yourself: "Weren't we automating the tests because it is fast? Why are my tests running so slow!?!". Then it is time to review your code and to optimize it a bit.
In this post, I want to discuss low level optimization. As WinRunner does not have a smart compiler (it is more an interpreted language then a compiled one), we have to do all optimizations by ourself.
1. Order of function calls in a condition
When you use and and or constructs in conditions ('if' statements for example), you have to think about the order:
Let's say, we have two checks, a time consuming (fncSlowCheck) and a fast one (fncFastCheck).
When you use an AND or an OR construct, you need to put the fastest compare first:
if (fncFastCheck() == E_OK && fncSlowCheck() == E_OK) { ... }
if (fncFastCheck() == E_OK || fncSlowCheck() == E_OK) { ... }
WinRunner will first evaluate the fast function. When this evaluates to false, the second function will not be called in case of the AND construction, because the total if statement can never become TRUE, and the if statement is stepped over.
With the OR, it is just the other way around. When the first function evaluates to TRUE, the second function will not be called.
This behavior is called lazy evaluation and is something you have to keep in mind when you use functions in conditional statements that can impact the application under test.
if (edit_set(myEdit1, "foo") != E_OK && edit_set(myEdit2, "bar") != E_OK) {
write2report("Something went wrong setting myEdit1 and/or myEdit2", ERROR)
}
Besides this is a crappy way of reporting, the second edit will never be set as soon the first edit_set() evaluates to an error.
2. Use switch / case constructs. They are fast!
The reason why switch / case are fast is because jmp (jump) commands in assembly are faster than cmp (compare) commands used with each loop iteration. Combined with the so called "fall through" mechanism, they cannot be beaten by loops. For a detailed article, see Duff's device on wikipedia.
3. ++i is faster than i++
Make it a good habit to use ++{variable} in your for constructs:
for (i = 0 ; i < 100 ; ++i)
I absolutely noticed the difference since I had some large multi dimensional arrays I had to iterate through.
One note: Keep in mind the order of handling of the incrementation. Do not blindly search and replace all {variable}++ with ++{variable}, this will mess up your test.
4. Calls to external functions can be slow
When you use external functions in time critical processes, it is a good habit to check the performance of these functions. Once, I had external and() and or() functions (they are not provided by WinRunner) and I used them to make a bit collection of matches of a table row. With each table row check, the correct bit was set to 1 or 0 in case of a match or a non-match.
But this rowcheck function was very slow. More then 3 minutes for a 25x25 table for example.
First, I didn't bother about the bad performance, we did a lot within a check: Negative checks, regular expressions and other exotic stuff to support the testers. But the rowcheck function got used more and more and the long idle times became annoying.
We created a lookup table for the powers of 2, optimized the loops and conditional statements, but it still underperformed.
Then, we measured the time for a "x = and(a, b);" call and it was 1 tenth of a second. This means more then one minute in case of 25x25 checks, and we not only used it onced, but three times in one iteration.
I assumed that the and() function had to be fast, because it was fast in C, the language the external lib was written in.
After changing the bit collection through and()s and or()s to an array and performing calculations on the array (the product as an and(), the sum for an or()) the performance was increased to 23 seconds for a 25x25 table check. Even with regular expression and negative testing in place.
5. Function calls can be slow
Whenever a function is called, a function is created on the stack, initialized, executed and destroyed. Processors are fast nowadays, but it still takes some time.
I used to use isEmpty({variable}) and isNotEmpty({variable}) for a check on empty or not. The only thing the function did was:
public function isEmpty(inValue) {
return (inValue == "");
}
It seemed smart on that moment, because if we created other definitions for empty, we just have to enter it in the function to implement it everywhere in the system. Unfortunately, we never came up with other definitions. We noticed, the isEmpty(myVariable) function was seven times as slow then a normal myVariable == EMPTY statement (EMPTY equals "" in our system).
With this knowledge we replaced all isEmpty() functions in time critical functionality, such as the table row search function mentioned above.
A little side note:
myVariable == EMPTY
or
myVariable == ""
does not make any difference in WinRunner.
Last word
As mentioned before, this is how you optimize code on a very low level. As long as your design is not right, use senseless synchronization timers or bad synchronization mechanisms, the performance of your test will not increase significantly. Optimization must be in balance on all levels of the test process; From scripting automated tests to requirements and risks.
Labels:
code optimization,
external functions,
function calls,
mercury,
tricks,
winrunner
June 2, 2008
QTP and Tricks with Classes
As a hardcore WinRunner test automater, I still not like the "one line, one statement" approach of VBScript, but the one thing I like is working with classes. I'm still playing around with it a little bit and I learn a bit each day. The thing I did today isn't new I think, but for me it was.
A little about my situation. We are (ab)using QTP as a dataentry tool. We can only use the frontend, because the backend is protected. (so long for third party software). There will be a backend interface soon, but soon in the ICT is still counted in multiple months and a quicker solution had to be found. Maybe that is why QTP is chosen. On the contrary, I think the program manager felt for the 'Q'. Right now, the data is coming from multiple sources and will be directed to the frontend with QTP.
And this is where I discovered a neat application of classes for the use in dataentry. I created a class, let's say "customer" and I redirected the class into a function as a method of that class.
Time for an example:
First, my class 'customer':
class customer
public name
public address
public phone
end class
and my function to enter the customer into the frontend:
public function enterCustomer(byRef myCus) ' Use byRef for speed and to save memory
call navigateToCustomerCreate() 'I like functions more then subs
with Browser(zzz).Page(yyy)
.WebEdit("Name").Set myCus.name
.WebEdit("Address").Set myCus.address
.WebEdit("Phone").Set myCus.phone
end with
end function
now, I added a function into the customer class to enter the customer:
class customer
public name
public address
public phone
public function enterData()
call enterCustomer(me) 'The 'me' is referring to it's own class object 'customer'
end function
end class
This creates a method for my customer object to enter my data.
Because a customer can have multiple accounts, I created a class account and that one is added as a dynamic array in the customer class. And for data integrity I created a validate method that checks if all mandatory fields are filled and if all data is in the correct formatting (hurray for regular expressions).
But I can add as much functionality as I like in a ordered way, the only call I have to make to get it all working is using the enterData() method in my driver script for entering all my data into the frontend.
The main reason why I use this method, is that I only have to write an import function for all three types of import (plain text, xml and through an ODBC connection) and redirect the data into the class. At the end I call myCustomer.enterData() and it is done.
In the mean time, testing my functions is very easy, I just create an object of the correct class and test the function by calling enterCustomer(myCustomer). I don't have to bother validation rules because they are only executed when the enterData() method is called. And because the method validate() is a public method in the customer class, I do not have to enter the data when I want to test the validation of it.
It seems a lot of work for only adding a customer but in the real situation, the datamodel contains a lot more objects with a lot more fields. This way, it keeps my code clean, structured and also important: testable.
Labels:
classes,
data entry,
mercury,
QTP,
testability,
tricks
June 1, 2008
QTP and Four Ways of creating Classes
QTP isn't build for the use of classes. If it was, we had a separated file where we could store our classes, a viewer extension and a set of exotic functions with almost enough functionality to handle our custom classes.
But we haven't have any of them, so we have to fiddle around with methods Mercury (I refuse to call them HP until their service is to the level we are used to experience) thought we are not capable of to use: 'Coz we're testers - not programmers.
These are four methods to implement classes. I found the last three on the net, the source is just down each method.
1. Just put them in your action script
And enjoy the experience of non-reusable classes. Just the main benefit of a class: reusability is taken away from you, because with each new action script, you have to define your class again (if you want to use it of course). This will only suit you if you only have one action script.
This is the case on my current project, because we don't use Quality Center and we use the action script more as the main driver then as what it is meant to be.
I still rather don't want to use it in the way I just described, because with only ten classes in our data model it is still getting crowded in the action script.
2. Put them in a function script
You can put your class into a separate function script and just add it to your project. But QTP wouldn't be QTP if this worked on first glance. As long as you only put it in there as a class, it wouldn't work, because in some way the class is seen as a private on that function library. It will not be recognized in other parts of your script.
The work around:
First, make a class
class prototypeContact
public name
public address
public phone
end class
and create a function (in the same library), setting an object as that class and returning that object:
public function Contact()
set Contact = new prototypeContact
end function
Now you can use your class by calling the function with a newly created object:
set myContact = Contact()
The function Contact() will assign a new object to myContact of the class prototypeContact.
First found on The Software Inquisition.
3. Put them in an external script
Create your classes in a new script and save it as .vbs file. From your main script (or action script), do a call to this library with ExecuteFile().
This works with one major drawback, you'll have a hard time debugging. As soon an error is raised, it will mention the line of your main script were ExecuteFile() was called and that's it.
First found on a comment on an article of The Software Inguisition.
4. Load them dynamically
This is the most complex way, but also the one with the most potential. I will not go into the deepest detail, you can read that in the original article, but I will explain the principle:
1. Dynamically create a text string that contains the class definition.
2. Execute the text string with the command "Execute" (for the WinRunner and Javascript readers: It does the same as the eval() function).
3. Now you can set an object as the just created class.
Because you create the string dynamically, you can adapt the class on the fly. I wouldn't dare to talk about inheritance or polymorphism, but you can imagine the power of runtime named classes, variables, objects, functions and properties.
There is still the drawback of item 3: It gives you a hard time debugging it when something went wrong. And that is a very big drawback if you think about bugs that can enter dynamic written code.
First found in an article on the blog of Stefan Thelenius.
Final Word
Not all methods are working on each release of QTP. ExecuteFile() for example is available as of version 9.0. You'll have to find out what method suits you best.
Labels:
classes,
dynamic class creation,
QTP
WinRunner to QTP transition: Pitfalls to avoid (part I)
Just since a while I am making my test automation scripts in Quick Test Pro. After working and dreaming in WinRunners TSL for six years, this was quite a change. Things that were easy in WinRunner, became not that easy in QTP, but the use of classes and objects made things better.
Down here some issues I had to cope with.
No incrementation shortcut
As obvious as hell, I cannot get used to the lack of ++, --,+=, -= and other short ways to increment, multiply etc. Nothing else to do then pointlessly call i = i + 1 on an (expensive) single line of code.
No 'continue' in loops
Within WinRunner, I used 'continue' just as often as the 'break' command. There is no substitute in QTP for this command, so you have to think out your flow properly, because you can only exit a loop (by 'exit {looptype}') and not start from the beginning from anywhere within the loop.
No fall-through in 'select case' flow
This becomes more and more a "drawback" list of QTP, but the thing I miss is the fall through mechanism of switch/case or select/case. Of course it is called Visual Basic, and starters will often forget a 'break;' statement at the end of a case, but the C like switch is very powerful at the moment you get used to it.
One time function evaluation in QTP
This one is less obvious, but important for WinRunner users. In WinRunner you could write code like this:
while (obj_exists(myObj) != E_OK)
web_refresh(myFrame);
Each time the while loop is entered and looped, the obj_exists() function will be called and evaluated. Within QTP a function that is called as part of the initiation of the loop will only be evaluated once. In this case the loop will never be exited when the object is not available the first time the loop is entered.
All functions in an if statement will be handled in QTP
In C like languages, if statements are lazy. An example:
if (set_window(myWin, 1) == E_OK && obj_exists(myObj, 10) == E_OK) { ...
WinRunner would evaluate the first function (the setting of the window) and when this results in an error, the other function in the if statement is skipped because of the AND: the if statement can never evaluate to TRUE anymore, so processing the other evaluation makes no sense.
Visual Basic like languages like QTP don't bother this kind of logic. In such kind of construct, QTP will evaluate both functions, no matter the outcome.
Labels:
pitfalls,
QTP,
transition,
winrunner
WinRunner and why I like TSL
I like TSL. Not because of its lack of classes, structs, function pointers or other fancy eighties C stuff, but because you can write powerful code on a single line. I like it even more since I am now developing my test automation in QTP. Yeah, that's right, the tool that uses crippled Visual Basic Script.
One nice thing in WinRunner is that you can use assignments inside functions or validations like:
for(i = 0; (rc = obj_exists(thisButton = “{class: push_button, location:”i”}”)) == E_OK; ++i)
button_press(thisButton);
pause(i-- “ button” (i != 1 ? “s were” : “ was”) “ pressed.”);
The three line above contains interesting code, I'll walk through it.
First, this contains an assignment in function:
obj_exists(thisButton = “{class: push_button, location:”i”}”);
And second, an assignment on validation like:
(rc = obj_exists(myObj)) == E_OK
thisButton = “{class: push_button, location:”i”}” is replaced by myObj to make it more readable.
Notice the parenthesis around the assignment; Normally assignments have a lower precedence, that is why we need it.
(Explanation:
If you use code like this:
rc = obj_exists(myObj) == E_OK
first obj_exists(myObj)) == E_OK is evaluated to TRUE or FALSE. Since rc gets that value assigned, rc becomes FALSE in case of a none existence of the object and TRUE otherwise, making the statement as buggy as hell; FALSE and E_OK both have the same value: 0, causing rc getting a E_OK value in case of a not existing myObj.)
The last line contains a short cut for an if else statement:
(validation ? value if true : value if false)
This is great when you want to write fast code in a (log)message. Otherwise the same line would take you at least four lines if you want to state it properly.
1. if (i == 1)
2. pause("1 button was pressed.”);
3. else
4. pause(i "buttons were pressed.");
The code in the example above is nothing worth in the sense of readability, but more to show the power of writing C-like lines of code. I would challenge the same functionality in a Visual Basic script like language. I think it will be 5 times more lines of code and a lot of debugging, since it is easily forgotten that VB(S) functions in "for" statements are evaluated only once. More on this another time.
Subscribe to:
Posts (Atom)