Assume you have some data that tells your scripts how to function when they run a particular set of actions. Instead of this logic being stored in a test script, common action sets are stored off in a data source. In this case, I’ll use Excel but you can use any data source at all. One thing I’ve found fairly effective in terms of an approach here is to create a class that contains the methods for building a dictionary that will essentially bring in the “action sets.” Here an action set refers to the data plus the test actions to take with that data against the application.
Let’s say I have data that looks like this:

To reference that data, after I have brought it into my framework, I ultimately want to use simple commands like this:
1 2 3 4 5 |
testFixture("SCREEN") testFixture("OBJECT") testFixture("DESCRIPTION") testFixture("ACTION") testFixture("DATA") |
Those will give me the current value for whatever I’m querying. More specifically those lines are essentially asking this:
- What screen am I on?
- What object should I be dealing with?
- (Any help on the locator description would be great.)
- What action should I take with that object?
- Assuming I need data for that action, what is that data?
Those are the context of my action. When all of that data is taken together, I have a test fixture. In order to make this work, I have to load the data into a dictionary type object. Here’s what that would look like:
1 |
Set currentFixture = testContext.Load("TestFixture.xlsx", "ActionSet", 2) |
Here I just pass in the name of the workbook, then the name of the worksheet within that workbook to read, and finally a row from that worksheet. What that’s basically saying is:
- “I want to get the second row of the ActionSet worksheet from TestFixture.xlsx and make that the current test fixture.
Okay, great, but what’s testContext? What’s the Load method a part of? Well, testContext is an instance of a class and Load is a method on that class. Here’s an example of what that class might look like. First I declare an instance of a class:
1 |
Public testContext : Set testContext = New clsTestActions |
Here is one possibly implementation of the class that testContext is an instance of:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
Class clsTestActions Private m_testCollect Public thisWorkbook, thisSheet, thisRow Private Property Let testCollect(ByVal thisValue) Set m_testCollect = thisValue End Property Private Property Get testCollect() Set testCollect = m_testCollect End Property Public Default Function Load(workbook, sheet, row) With Me .thisWorkbook = workbook .thisSheet = sheet .thisRow = row End With BuildContext : Set Load = testCollect End Function Private Function BuildContext Dim connString, recordSet, query, counter Set connString = CreateObject("ADODB.Connection") Set recordSet = CreateObject("ADODB.RecordSet") connString.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _ "Data Source=" & Me.thisWorkbook & ";" & _ "Extended Properties=""Excel 12.0 Xml"";" query = "SELECT * FROM [" & Me.thisSheet & "$]" recordSet.Open query, connString, 3, 3, 1 For counter = 2 to thisRow - 1 recordSet.MoveNext Next testCollect = CreateObject("Scripting.Dictionary") For counter = 0 to recordSet.Fields.Count - 1 With testCollect .Add "" & recordSet(counter).Name, "" & recordSet.Fields(counter) End With Next End Function End Class |
What this class allows me to do is build my action set (and thus my test fixture) at run-time and I can use the testFixture object to query what was returned from the row. Therein, however, lies a problem. When this script runs it only grabs one row. That’s wonderfully fantastic if I just want to grab that one row. Yet the action set is all of those rows taken together. So clearly the idea here is that I want to grab a set of rows and have those be my test fixture.
That means I want to modify the class to handle that new requirement but I want keep the interface to the class the same. The major modification is to the BuildContext method as well as the addition of a new method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
Class clsTestActions ... Private Function BuildContext Dim connString, recordSet, query, counter Set connString = CreateObject("ADODB.Connection") Set recordSet = CreateObject("ADODB.RecordSet") connString.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _ "Data Source=" & Me.thisWorkbook & ";" & _ "Extended Properties=""Excel 12.0 Xml"";" query = "SELECT * FROM [" & Me.thisSheet & "$]" recordSet.Open query, connString, 3, 3, 1 'For counter = 2 to thisRow - 1 ' recordSet.MoveNext 'Next testCollect = CreateObject("Scripting.Dictionary") For rowcount = 2 to thisRow For counter = 0 to recordSet.Fields.Count - 1 With testCollect .Add "" & recordSet(counter).Name, "" & recordSet.Fields(counter) End With Next ProcessSet(testCollect) testCollect.RemoveAll recordSet.MoveNext Next End Function Private Function ProcessSet(obj) items = obj.Items For i = 0 To UBound(items) If items(i) <> "" Then MsgBox items(i) End If End Function End Class |
Here you can see the changes I had to make highlighted. Notice, however, that the line you would use to call this functionality is still the same, in terms of the information you would pass:
1 |
Set currentFixture = testContext.Load("TestFixture.xlsx", "ActionSet", 6) |
But now that row number at the end tells the test logic how many rows to process — in other words, how much of the action set to grab in and do something with. In the first implementation it was just indicating which row to grab. Now it’s saying what range of rows to grab. In the above case, it will grab from row six back to the start (excluding the header row, of course). Also, as you can see, in the ProcessSet method I was just printing out the items with a message box but this is where logic could go to actually do something with the data returned, such as drive the application.
Note that may action sets could be in any format. For example, they could look like this:

Obviously in this case I would need mechanisms to parse out those statements and while that may be a bit of extra work — actually, more than a bit — it is certainly possible.
Now, you may ask: is the ProcessSet function the best place to handle the actual logic? Well, it seems that a good way of utilizing an object of type clsTestActions would be to place it within a framework object that acts as a test runner. Here’s an example of how this could be utilized in terms of a framework:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Public currentFixture Class clsTestRunner Public Default Function Begin Setup '... runner logic goes here ... Cleanup End Function Private Function Setup Set currentFixture = testContext.Load("TestFixture.xlsx", "ActionSet", 6) End Function Private Function Cleanup Set currentFixture = Nothing End Function End Class |
The idea I hope I conveyed here is focusing on a single structure — in this case, currentFixture — to store all my test actions in a descriptive way. This makes it easier to know what data is being input to my application’s screen fields and what actions are being taken. You can also see that the test runner is designed to follow a setup and teardown pattern. In this case, the setup is getting the action set that will be executed against the application. The teardown here isn’t so much about getting the system itself back to some defined state, but rather making sure that my test fixture is cleared out.
I’m still playing around with these ideas but so far I’ve found this to be a powerful approach to allowing people to write sets of actions in certain ways and having a QTP framework read those actions and then process them accordingly.