Implementing a Generic Iterator with Function Pointers

Article Tools

 

Abstract

This article describes a powerful technique that exploits my previously outlined innovation – Function Pointers in VB Script – to build a generic mechanism having the capacity of executing components repeatedly: the generic iterator.

 

Introduction

Executing processes repeatedly is a common theme in software development, one that is at the core of data processing in IT as well as other domains (RT, etc.). Automation developers also have the need to implement iteration code structures (ubiquitously called loops) in their scripts and functions, and this is not surprising because “… testing automation … is, indeed, a specific kind of software development …” (Implementing a GUI Layer with Classes, Bar-Tal, 2008), though many times I have encountered QA managers who count automation as a testing activity. For example, iterations are used for table data verification (GUI level), file processing, and for data-driving tests (i.e., executing the same test flow with different data values). Moreover, the ability to execute a previously coded procedure (or function) repeatedly is at the heart of automation; it is exactly what makes automation so appealing as a true alternative to putting a human tester to perform the tedious task of filling forms, pressing buttons and verifying results hundreds and even thousands of times. All the more, since such tasks are also repeated periodically – each firm having its own application version release policy (quarterly, semi-annually, and even weekly and bi-weekly schedule). Though basically such iteration structures are not difficult to implement, they are a major source of defects found in the code produced by developers. One central reason for this are faulty loop exiting conditions, which not always are well defined. Consequently, it would be of great importance to have a method of implementation that eliminates the need for coding the iteration structure again and again.

In previous articles we have seen that it is possible to exploit Object Oriented Design Patterns in order to improve the code used in our automated tests with regard to maintainability, readability, scalability, extensibility, reusability and testability. Moreover, it has been argued that using Function Pointers can help us to improve the performance of our code, and that this improvement correlates positively with the number of times a function is called (Bar-Tal, 2008). Combined with the fact that with other (OO) languages professionals define and implement iterator interfaces successfully and ubiquitously, all the above suggest that developing such an interface would most probably be worthwhile. As aforementioned, the method outlined here below  is based on the basic techniques described in the article Function Pointers in VB Script (revised) (Bar-Tal, 2007, 2008). Hence that, before continuing, it is strongly recommended that the reader makes sure the ideas of function pointers and design patterns are well understood.

In this article I will propose a generic mechanism to handle iterations of executable code. I will attempt to show that using the method suggested here can have a beneficial impact on the efforts invested in development, testing, debugging and fixing defects, as it offers a service that can be used with ease. All it requires is the implementation of some very simple interfaces, as we will show in the next section.

Before delving into the actual implementation, let us define some terms. A generic iterator is a device capable of executing repeatedly a functionally independent executable component that performs a process upon a given object (which can be any data entity, such as a number or a mobile phone call data record). Since iterations are meaningful with variable inputs, it should be clear that the given process would act upon a different object in each iteration. Hence, the iterator would require at least two arguments: a collection of objects (operands) and a process (the operator). A process can be as simple as an arithmetic operation involving two numbers, but it can also be complicated as the calculation of a mobile customer bill, and more. Within the scope of the framework described hereby, the executable component that implements a process is built using the Command Wrapper design pattern. Functionally independent means that the specific blocks of code that we need to execute repeatedly are built without referencing any internals of the iterator device. In other words, the generic iterator does not care at all about the specific content included in the executable it activates, and the executable component does not need to take into account how the iterator that launches it is internally built. The only point in which both sides must be compatible is – as would be expected – with regard to their interfaces.

 

Method

In order to build the generic iteration mechanism we need to define the entities that will be involved in our implementation.  Three entities must be defined:

  • The Iterator;
  • The required process or operation (packed using the Command Wrapper design pattern);
  • The object (class instance) upon which the process or operation should be applied.

Let us define the Iterator class as follows:

   1: '-------------------------------------------------------------------------------

   2: Class Iterator

   3: '-------------------------------------------------------------------------------

   4: 'Class: Iterator

   5: 'Encapsulates the Iterator Object

   6: '

   7: 'Remarks:

   8: '    N/A

   9: '

  10: 'Owner:

  11: '    Meir Bar-Tal, SOLMAR Knowledge Networks Ltd.

  12: '

  13: 'Date:

  14: '    17-Jan-2009

  15: '

  16: '-------------------------------------------------------------------------------

  17:     '-------------------------------------------------------------------------------

  18:     Public Default Function Run(ByRef objCollection, ByRef ptrFunction, ByVal strExitCondition)

  19:     '-------------------------------------------------------------------------------

  20:     'Function: Run

  21:     'Performs n iterations of ptrFunction on the objCollection items where n=objCollection.count.

  22:     '

  23:     'If the strExitCondition holds true, the next iteration is not performed and the function is exited.

  24:     '

  25:     'Remarks:

  26:     '

  27:     'Arguments:

  28:     '    ByRef objCollection - As Scripting.Dictionary

  29:     '

  30:     '    ByRef ptrFunction - As Function Pointer (to implement callback)

  31:     '

  32:     '    ByVal strCondition - String with condition to be evaluated.

  33:     '

  34:     'Returns:

  35:     '    Scripting.Dictionary with the results of each iteration

  36:     '

  37:     'Owner:

  38:     '    Meir Bar-Tal, SOLMAR Knowledge Networks

  39:     '

  40:     'Date:

  41:     '    17-Jan-2009

  42:     '

  43:     '-------------------------------------------------------------------------------

  44:         Dim count, items, ix, str, dicResults

  45:         

  46:         'Create a Dictionary to store the results for each iteration

  47:         Set dicResults = CreateObject("Scripting.Dictionary")

  48:         

  49:         'Get the collection count

  50:         count = objCollection.Count

  51:         

  52:         'Get the collection items

  53:         items = objCollection.Items

  54:         

  55:         ix =  0

  56:         Do While ix < count

  57:             'Check if the exit condition holds true

  58:             If Eval(strExitCondition) Then

  59:                 dicResults(ix) = "Iteration " & ix & " not performed." & vbNewLine & "Condition '" & strExitCondition & "' holds true. Exiting iterator."

  60:                 Exit Do

  61:             End If

  62:             

  63:             'This statement performs the process/operation on the current item

  64:             dicResults(ix) = ptrFunction(items(ix))

  65:             

  66:             'Increment the counter

  67:             ix = ix + 1

  68:         Loop

  69:         

  70:         'Return Dictionary with the results

  71:         Set Run = dicResults

  72:     '-------------------------------------------------------------------------------

  73:     End Function

  74:     '-------------------------------------------------------------------------------

  75: '-------------------------------------------------------------------------------

  76: End Class

  77: '-------------------------------------------------------------------------------

Snippet 1. The Iterator Class

 

The reader certainly noticed the way an additional exit condition expression is passed to the Iterator (as a String) and how it is evaluated at the beginning of each iteration, before the actual operation is performed. This is also a powerful trick: for instance, we may pass to the Iterator Run method an exit condition that checks if an error occurred (“Err.Number <> 0″), after adding the On Error Resume Next and On Error Goto 0 statements at the appropriate places within the method.

Now, by implementing the Command Wrapper design pattern, let us define the arithmetic operations of addition, subtraction, multiplication, division, raising to a power and calculating the square root:

   1: '-------------------------------------------------------------------------------

   2: Class Add

   3: '-------------------------------------------------------------------------------

   4:     '-------------------------------------------------------------------------------

   5:     Public Default Function Exec(ByVal arrInt)

   6:     '-------------------------------------------------------------------------------

   7:     'Function:    Exec

   8:     'Executes the Command Wrapper's functionality

   9:     '

  10:     'Remarks:

  11:     '

  12:     'Arguments:

  13:     '    ByVal arrInt - As Array

  14:     '

  15:     'Returns:

  16:     '    Result of operation

  17:     '

  18:     'Owner:

  19:     '    Meir Bar-Tal, SOLMAR Knowledge Networks

  20:     '

  21:     'Date:

  22:     '    17-Jan-2009

  23:     '

  24:     '-------------------------------------------------------------------------------

  25:         Exec = arrInt(0)+arrInt(1)

  26:     '-------------------------------------------------------------------------------

  27:     End Function

  28:     '-------------------------------------------------------------------------------

  29: '-------------------------------------------------------------------------------

  30: End Class

  31: '-------------------------------------------------------------------------------

Snippet 2. Addition

   1: '-------------------------------------------------------------------------------

   2: Class Subtract

   3: '-------------------------------------------------------------------------------

   4:     '-------------------------------------------------------------------------------

   5:     Public Default Function Exec(ByVal arrInt)

   6:     '-------------------------------------------------------------------------------

   7:     'Function:    Exec

   8:     'Executes the Command Wrapper's functionality

   9:     '

  10:     'Remarks:

  11:     '

  12:     'Arguments:

  13:     '    ByVal arrInt - As Array

  14:     '

  15:     'Returns:

  16:     '    Result of operation

  17:     '

  18:     'Owner:

  19:     '    Meir Bar-Tal, SOLMAR Knowledge Networks

  20:     '

  21:     'Date:

  22:     '    17-Jan-2009

  23:     '

  24:     '-------------------------------------------------------------------------------

  25:         Exec = arrInt(0)-arrInt(1)

  26:     '-------------------------------------------------------------------------------

  27:     End Function

  28:     '-------------------------------------------------------------------------------

  29: '-------------------------------------------------------------------------------

  30: End Class

  31: '-------------------------------------------------------------------------------

Snippet 3. Subtraction

   1: '-------------------------------------------------------------------------------

   2: Class Multiply

   3: '-------------------------------------------------------------------------------

   4:     '-------------------------------------------------------------------------------

   5:     Public Default Function Exec(ByVal arrInt)

   6:     '-------------------------------------------------------------------------------

   7:     'Function:    Exec

   8:     'Executes the Command Wrapper's functionality

   9:     '

  10:     'Remarks:

  11:     '

  12:     'Arguments:

  13:     '    ByVal arrInt - As Array

  14:     '

  15:     'Returns:

  16:     '    Result of operation

  17:     '

  18:     'Owner:

  19:     '    Meir Bar-Tal, SOLMAR Knowledge Networks

  20:     '

  21:     'Date:

  22:     '    17-Jan-2009

  23:     '

  24:     '-------------------------------------------------------------------------------

  25:         Exec = arrInt(0)*arrInt(1)

  26:     '-------------------------------------------------------------------------------

  27:     End Function

  28:     '-------------------------------------------------------------------------------

  29: '-------------------------------------------------------------------------------

  30: End Class

  31: '-------------------------------------------------------------------------------

Snippet 4. Multiplication

   1: '-------------------------------------------------------------------------------

   2: Class Divide

   3: '-------------------------------------------------------------------------------

   4:     '-------------------------------------------------------------------------------

   5:     Public Default Function Exec(ByVal arrInt)

   6:     '-------------------------------------------------------------------------------

   7:     'Function:    Exec

   8:     'Executes the Command Wrapper's functionality

   9:     '

  10:     'Remarks:

  11:     '

  12:     'Arguments:

  13:     '    ByVal arrInt - As Array

  14:     '

  15:     'Returns:

  16:     '    Result of operation

  17:     '

  18:     'Owner:

  19:     '    Meir Bar-Tal, SOLMAR Knowledge Networks

  20:     '

  21:     'Date:

  22:     '    17-Jan-2009

  23:     '

  24:     '-------------------------------------------------------------------------------

  25:         If arrInt(1) <> 0 Then

  26:             Exec = arrInt(0)/arrInt(1)

  27:          Else

  28:              Exec = "Error: Division by zero."

  29:          End If

  30:     '-------------------------------------------------------------------------------

  31:     End Function

  32:     '-------------------------------------------------------------------------------

  33: '-------------------------------------------------------------------------------

  34: End Class

  35: '-------------------------------------------------------------------------------

Snippet 5. Division

   1: '-------------------------------------------------------------------------------

   2: Class Power

   3: '-------------------------------------------------------------------------------

   4:     '-------------------------------------------------------------------------------

   5:     Public Default Function Exec(ByVal arrInt)

   6:     '-------------------------------------------------------------------------------

   7:     'Function:    Exec

   8:     'Executes the Command Wrapper's functionality

   9:     '

  10:     'Remarks:

  11:     '

  12:     'Arguments:

  13:     '    ByVal arrInt - As Array

  14:     '

  15:     'Returns:

  16:     '    Result of operation

  17:     '

  18:     'Owner:

  19:     '    Meir Bar-Tal, SOLMAR Knowledge Networks

  20:     '

  21:     'Date:

  22:     '    17-Jan-2009

  23:     '

  24:     '-------------------------------------------------------------------------------

  25:         Exec = arrInt(0)^arrInt(1)

  26:     '-------------------------------------------------------------------------------

  27:     End Function

  28:     '-------------------------------------------------------------------------------

  29: '-------------------------------------------------------------------------------

  30: End Class

  31: '-------------------------------------------------------------------------------

Snippet 6. Raise to a Power

   1: '-------------------------------------------------------------------------------

   2: Class SquareRoot

   3: '-------------------------------------------------------------------------------

   4:     '-------------------------------------------------------------------------------

   5:     Public Default Function Exec(ByVal varNumber)

   6:     '-------------------------------------------------------------------------------

   7:     'Function:    Exec

   8:     'Executes the Command Wrapper's functionality

   9:     '

  10:     'Remarks:

  11:     '

  12:     'Arguments:

  13:     '    ByVal varNumber - As Variant (Numeric))

  14:     '

  15:     'Returns:

  16:     '    Result of operation

  17:     '

  18:     'Owner:

  19:     '    Meir Bar-Tal, SOLMAR Knowledge Networks

  20:     '

  21:     'Date:

  22:     '    17-Jan-2009

  23:     '

  24:     '-------------------------------------------------------------------------------

  25:         If varNumber <> 0 Then

  26:             Exec = Sqr(varNumber)

  27:          Else

  28:              Exec = "Error: Square root for zero."

  29:          End If

  30:     '-------------------------------------------------------------------------------

  31:     End Function

  32:     '-------------------------------------------------------------------------------

  33: '-------------------------------------------------------------------------------

  34: End Class

  35: '-------------------------------------------------------------------------------

Snippet 7. Square Root

 

Now let us define the objects on which these operations will be performed. As we are in the realm of arithmetic operations, obviously the objects must be numbers.

   1: '-------------------------------------------------------------------------------

   2: Class Number

   3: '-------------------------------------------------------------------------------

   4:     Private m_intNumber

   5:  

   6:     '-------------------------------------------------------------------------------

   7:     Public Default Property Get Value()

   8:     '-------------------------------------------------------------------------------

   9:         Value = m_intNumber

  10:     '-------------------------------------------------------------------------------

  11:     End Property

  12:     '-------------------------------------------------------------------------------

  13:     '-------------------------------------------------------------------------------

  14:     Public Property Let Value(ByVal intNumber)

  15:     '-------------------------------------------------------------------------------

  16:         m_intNumber = intNumber

  17:     '-------------------------------------------------------------------------------

  18:     End Property

  19:     '-------------------------------------------------------------------------------

  20: '-------------------------------------------------------------------------------

  21: End Class

  22: '-------------------------------------------------------------------------------

Snippet 8. The Number Class

 

We need a “constructor” function to create initialized instances of Number, as follows:

   1: '-------------------------------------------------------------------------------

   2: Public Function CreateNumber(ByVal intNumber)

   3: '-------------------------------------------------------------------------------

   4: 'Function:    CreateNumber

   5: 'Creates a new instance of class Number

   6: '

   7: 'Remarks:

   8: '

   9: 'Arguments:

  10: '    ByVal intNumber - As Integer

  11: '

  12: 'Returns:

  13: '    Initialized Number object

  14: '

  15: 'Owner:

  16: '    Meir Bar-Tal, SOLMAR Knowledge Networks

  17: '

  18: 'Date:

  19: '    17-Jan-2009

  20: '

  21: '-------------------------------------------------------------------------------

  22:     Set CreateNumber = New Number

  23:     CreateNumber.Value = intNumber

  24: '-------------------------------------------------------------------------------

  25: End Function

  26: '-------------------------------------------------------------------------------

Snippet 9. The CreateNumber Function 

 

And to end this section, let us illustrate how the entities defined above work in practice. Naturally, the example will deal with performing certain types of calculations to a list of numbers.

   1: 'Create a factor number to be used in the arithmetic operations

   2: Set factor = CreateNumber(5)

   3:  

   4: 'Create a collection of number pairs using a dictionary

   5: Set dic = CreateObject("Scripting.Dictionary")

   6: dic.Add "0", Array(CreateNumber(0), factor)

   7: dic.Add "1", Array(CreateNumber(1), factor)

   8: dic.Add "2", Array(CreateNumber(2), factor)

   9: dic.Add "3", Array(CreateNumber(3), factor)

  10: dic.Add "4", Array(CreateNumber(4), factor)

  11: dic.Add "5", Array(CreateNumber(5), factor)

  12: dic.Add "6", Array(CreateNumber(6), factor)

  13: dic.Add "7", Array(CreateNumber(7), factor)

  14: dic.Add "8", Array(CreateNumber(8), factor)

  15: dic.Add "9", Array(CreateNumber(9), factor)

  16:  

  17: 'Create Iterator object

  18: Set objIterator = New Iterator

  19:  

  20: 'Set exit condition

  21: strExitCondition = "GlobalStatus = 2"

  22:  

  23: 'Set GlobalStatus variable

  24: GlobalStatus = 0

  25:  

  26: 'Create Multiplication Operation

  27: Set ptrFunction = New Multiply

  28: 'Print result

  29: Msgbox Join(objIterator(dic, ptrFunction, strExitCondition).Items, vbNewLine), vbOKOnly+vbInformation, "Iterator Example - Multiplication by " & factor

  30:  

  31: 'Create Addition Operation

  32: Set ptrFunction = New Add

  33: 'Print result

  34: Msgbox Join(objIterator(dic, ptrFunction, strExitCondition).Items, vbNewLine), vbOKOnly+vbInformation, "Iterator Example - Addition of " & factor

  35:  

  36: 'Create Subtraction Operation

  37: Set ptrFunction = New Subtract

  38: 'Print result

  39: Msgbox Join(objIterator(dic, ptrFunction, strExitCondition).Items, vbNewLine), vbOKOnly+vbInformation, "Iterator Example - Subtraction of " & factor

  40:  

  41: 'Create Division Operation

  42: Set ptrFunction = New Divide

  43: 'Print result

  44: Msgbox Join(objIterator(dic, ptrFunction, strExitCondition).Items, vbNewLine), vbOKOnly+vbInformation, "Iterator Example - Division by " & factor

  45:  

  46: 'Create Power Operation

  47: Set ptrFunction = New Power

  48: 'Print result

  49: Msgbox Join(objIterator(dic, ptrFunction, strExitCondition).Items, vbNewLine), vbOKOnly+vbInformation, "Iterator Example - Raise number to the Power of " & factor

  50:  

  51: 'Create a collection of numbers using a dictionary

  52: Set dic = CreateObject("Scripting.Dictionary")

  53: dic.Add "0", CreateNumber(100)

  54: dic.Add "1", CreateNumber(200)

  55: dic.Add "2", CreateNumber(300)

  56: dic.Add "3", CreateNumber(400)

  57: dic.Add "4", CreateNumber(400)

  58: dic.Add "5", CreateNumber(500)

  59: dic.Add "6", CreateNumber(600)

  60: dic.Add "7", CreateNumber(700)

  61: dic.Add "8", CreateNumber(800)

  62: dic.Add "9", CreateNumber(900)

  63:  

  64: 'Create Square Root Operation

  65: Set ptrFunction = New SquareRoot

  66:  

  67: 'Print result

  68: Msgbox Join(objIterator(dic, ptrFunction, strExitCondition).Items, vbNewLine), vbOKOnly+vbInformation, "Iterator Example - Square Root"

  69:  

  70: 'Dispose of Iterator

  71: Set objIterator = Nothing

Snippet 10. Using the Iterator

 

Note: The code is full of comments to make it easier for the reader to understand. In case of need, please comment below the article and I will reply and give assistance.

 

Summary

This article has shown how to build and use a generic iterator written in VB Script. I pinpointed the motivation for building such a mechanism and explained its advantages for improving our development practices. I wish to encourage the readers to put hands on and try to implement and use the techniques described here and in other articles at www.AdvancedQTP.com, and to share with the community their experience in these matters. As aforesaid, please don’t hesitate to contact us or post your questions here below.

Previous post.Net Objects, Runtime Hierarchies and Bizarre QTP Errors Next postNo Regular-Expressions for the Desktop Object

Related Posts

Post Your Comment

You must be logged in to post a comment.