Implementing a Generic Iterator with Function Pointers
Posted by admin - Jan 19, 2009 Articles, Meir Bar-Tal 0 0 Views : 542 Receive Updates For This Category
Article Tools
- Print this page
- Add Comment
- Send to Friend
- Last Updated on :
Jul 15, 2011
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.


