An Introduction to Data-Driven Testing in TestComplete
Preface
TestComplete offers considerable power and flexibility via its support for Data-Driven Testing. This paper will serve to do the following:
- Introduce the DDT concept.
- Give an overview of TestComplete’s support for DDT.
- Visit the DDT sample included with the TestComplete install.
Introducing Data-Driven Testing
In addition to its support for automation of unit, regression, distributed and other testing types, TestComplete offers support for traditional record-and-playback-style GUI and functional testing. While certainly not a comprehensive method of testing on its own, recordable scripts represent an excellent starting place for creating more robust and complete test projects. One of the complaints leveled against traditional script playback-type automated testing is that it is inflexible and that the test cases are not easily extended. To answer this argument, the Data-Driven Testing (DDT) methodology was born. Although this technique does not originate with TestComplete, it is a fairly straightforward concept and is easily implemented within TestComplete scripts.
The simplest explanation of DDT is this: data that is external to your functional test scripts is loaded and used to extend your test cases. One of the best examples is that of a customer order form. If you wished to populate the entry fields with multiple test cases without DDT, you would either need to record multiple test scripts (one for each different test case) or employ DDT. To use DDT in this scenario, you might record a single script, entering values into the various fields. Then, you could alter the script to accept variables, entering those variables into the data fields. Now you can call this script each time you want to add an order record, passing in a new set of data each time.
Here’s an example. Using the Orders.exe sample application provided with the TestComplete install, I recorded the following script to enter a customer record:
// Jscript function CreateCustomer() { var p, w p = Sys.OrdersProcess w = p.MainForm w.Activate() w.ToolButton5.Click(6, 7) w = p.OrderFrm w.Activate() w.CustomerName_Edit.Click() Sys.Keys("Jane Doe") w.Street_Edit.Click() Sys.Keys("1234 Anywhere") w.City_Edit.Click() Sys.Keys("Townsville") w.State_Edit.Click() Sys.Keys("CA") w.Zip_Edit.Click() Sys.Keys("98765") w.OKButton.Click() }
If we leave this script alone, it knows how to enter and save a new customer, but it’s the same customer (Jane Doe) every time we run the script. With a few changes, we can make this script far more flexible, and able to add any customer info we want:
// Jscript function CreateCustomer(aCustomerName, aStreet, aCity, aState, aZip) { var p, w p = Sys.OrdersProcess w = p.MainForm w.Activate() w.ToolButton5.Click(6, 7) w = p.OrderFrm w.Activate() w.CustomerName_Edit.wText = aCustomerName w.Street_Edit.wText = aStreet w.City_Edit.wText = aCity w.State_Edit.wText = aState w.Zip_Edit.wText = aZip w.OKButton.Click() }
Don’t worry about the details right now, but basically this alteration allows us to call CreateCustomer multiple times, passing in different customer data each time.
Using this sort of approach, we can create test projects that can be infinitely extended by simply adding new lines of text to a text file or a spreadsheet. The most simple of examples is expressed in figure 1, where data is read from an external source and fed line-by-line into the functional test script until there is no more external data.
Figure 1 - a simple DDT process.
In this way, new test cases that are added to the external data simply extend the loop for each new line of data. The DDT technique allows for some far more complex processes, but that goes beyond the scope of this paper. Future papers will examine more complex DDT scenarios.
This concludes the general introduction to the DDT concept. Next we’ll examine TestComplete’s support for this technique.
TestComplete's Support for Data-Driven Testing
Since DDT itself is very straightforward, TestComplete’s direct support for the technique is likewise quite straightforward. If you examine the TestComplete online help, under “data-driven testing”, you’ll find a fairly long help topic with links to external pages on the subject, but also links to five script routines. It’s worth noting that while these methods are listed with the DDT topics, they’re not the only routines you’ll need to perform DDT, but for now we’ll look at those five. They are GetCSVCount, GetCSVItem, CallMethod, SetValue and GetValue. The first two methods are ones that you’ll use quite a bit in DDT, the other three will see less frequent use unless you are getting into some fairly robust DDT scripting.
GetCSVCount and GetCSVItem work with comma-separated strings in order to provide an easy way to get to the values contained within the strings. This is called “parsing”. A comma-separated value (CSV) string might look something like this:
02,c,”John”,”Q”,”Public”,13,”Product 02”,”1234 Anywhere St.”,”San Antonio”,”TX”
You can see that each element of this string is separated by a comma. Each individual element of the string is a called a “field” or a “value”. The GetCSVCount and GetCSVItem methods work with strings like this. If we passed this string in to the GetCSVCount function, we’d get a return value of 10. There are ten discreet values in this CSV string. In addition, if we wanted to the get the customer’s first name, we could use GetCSVItem to do that.
Here’s an important thing to keep in mind: GetCSVItem is what’s known as “zero-based”. That means that the first item in the CSV string is considered number 0 rather than number 1. Likewise, if we wanted the last item, it would be item number 9, not number 10. Thus, if we wanted the customer’s first name, we’d have to ask for item number 2, not number 3, thus:
So, with these two functions, we’re able to easily retrieve values from CSV files, something that is quite useful if our external DDT data is in CSV text files or spreadsheets. Returning again to our customer/orders example, we might see the following. (This is pseudo-code only.)
// Jscript // this routine reads the external data and calls // EnterCustomerRecord for each line of data function ReadExternalData(aFileName) { var lsLine var lsCustomerName, lsAddr, lsCity, lsRegion, lsPostalCode // Read data line-by-line openFile(aFileName) while not EOF(aFileName) { lsLine = readLine(aFileName) // lsLine now contains a line of CSV test data if (GetCSVCount(lsLine) == 5 // make sure we have valid data { lsCustomerName = GetCSVItem(lsLine, 0) // get customer’s name lsAddr = GetCSVItem(lsLine, 1) lsCity = GetCSVItem(lsLine, 2) lsRegion = GetCSVItem(lsLine, 3) lsPostalCode = GetCSVItem(lsLine, 4) // this gets called for each record in the external data: EnterCustomerRecord(lsCustomerName, lsAddr, lsCity, lsRegion, lsPostalCode) } } closeFile(aFileName) } function EnterCustomerRecord(aCustomerName, aAddr, aCity, aRegion, aPostalCode) { var p, w p = Sys.OrdersProcess w = p.MainForm w.Activate() w.ToolButton5.Click(6, 7) w = p.OrderFrm w.Activate() w.CustomerName_Edit.wText = aCustomerName w.Street_Edit.wText = aAddr w.City_Edit.wText = aCity w.State_Edit.wText = aRegion w.Zip_Edit.wText = aPostalCode w.OKButton.Click() validateRecordEntry() // make sure data was entered correctly }
Don’t worry about trying to make this code actually work – it’s rather abstracted and is for demonstration only, but your actual DDT code might be similar. What this small amount of code does is very powerful and serves as a great example of a standard DDT scenario. While it’s also possible to do more complex tasks, which I’ll examine in future papers, this is a good starting place.
As for the other three methods covered in the help (CallMethod, SetValue and GetValue) I won’t talk about those now – they’re not necessary for this primer on DDT.
The part of this whole process that’s not clearly covered is that of actually reading the external data. That is in part because there are so many options. TestComplete can read your DDT data from text files, spreadsheets, database tables or from internal data arrays. To access your data, you can use ADO, OLE automation or standard text file access methods. Many of these storage options and access methods are shown in the DDT demo project included with TestComplete, which we’ll cover shortly. For now, just plan on getting to your data whichever way is most comfortable.
The DDT Sample Project
Included with the TestComplete sample test projects is a set of projects that demonstrate one approach to data-driven testing. If you look in the sample scripts directory (by default, it is at C:\Program Files\Automated QA\TestComplete 3\Samples\Scripts), you’ll see a folder labeled “DDT&NameMapping”. This folder contains the TestComplete DDT sample project (in Jscript, VBScript and DelphiScript). Pick the language with which you’re most comfortable, and open that project.
The first thing to keep in mind about this project is that it is actually demonstrating more than one technique. This can be confusing, but I’ll try to point out where the DDT techniques are actually used. Obviously, as the name implies, the demo is showcasing both DDT and the Name Mapping technique. It also makes use of the Object-Driven Testing panel, If you open the TestComplete Name Mapping panel, you’ll see that three different name mapping “builds” are available, depending on how your demo Orders.exe application is compiled. Make sure you select the appropriate build in this panel.
If you navigate to the Suites tab, and have a look at the Test Suites panel, you’ll see something like figure 2.
Figure 2 – the Test Suites panel.
This demo includes five different data sources to perform the same set of data-driven tests. Each of the nodes in this list (Internal Data, CSV simple, etc.) can be turned on or off to see the behavior for that data type. Notice that the Main Routine column in this panel shows what script routine is actually being executed for the specified node. Thus, if you wish to see how to access external data from a CSV text file (without using ADO), you might turn off all but the CSV simple node and examine the MainUnit.MainCSVSimple script routine. You’ll quickly notice that MainCSVSimple only serves to call the Main routine which, in turn, calls the MainTest routine. There are reasons for this, but for now, just know that the real work in this test project is being done by MainTest.
In figure 3, you can see a UML-style sequence diagram of the work flow of this DDT demo project.
Figure 3 – DDT demo sequence diagram.
The DDT&NameMapping demo is a bit complex, perhaps overly so for the simple concept of DDT, so I’ll break this down for you into smaller bits. If you look at these diagrams and follow along in the TestComplete DDT&NameMapping project, things should become clearer.
Figure 4 – OnStartTest.
In figure 4, you can see the first things that happen when the test is executed. The project’s OnStartTest event fires, and the DDT1_OnStartTest method implements the event handler. DDT1_OnStartTest in turn calls DefineOrderInfoClass (which creates a new object-driven testing helper class) and InitTestData (which populates an internal data array).
Figure 5 – main test routines.
Figure 5 begins when one of the five Mainxxx routines are called. Since this demo shows five different data sources for the same test runs, each of these routines (MainInternal, MainCSVSimple, etc.) takes a different approach to loading the data for the tests. You can use the diagrams and the demo project source code itself to discover how this demo project works, but I’ll just give you this heads-up: the demo doesn’t use DDT for standard data entry with customers and orders. Rather, the external data is used to validate the processing of the orders entry page. Thus, when each test case is read in from the DDT data, it is entered into the order page, and then the resulting calculations on that form are validated. It’s a slightly different approach for a DDT example, and though it’s not an approach that I usually use for demonstrations, it’s a perfectly valid example. Additionally, once you become more familiar with the strengths of DDT, this will be a great example of more robust usage.
Conclusion
This paper should serve as an introduction to data-driven testing and TestComplete’s support for it. In future papers, we’ll examine some other examples of the uses of DDT and some more powerful uses.