Editor’s Note: This article was written using the IBM® Rational® Functional Tester for Java™ and Web 6.1 and Windows XP Professional SP2. Code examples will be in Java, but all the concepts apply to the .NET version of Rational Functional Tester as well.
Abstraction, encapsulation, and other computer science buzzwords
I’m generally not big on using buzzwords, but since the power of a modularity framework is based on some rather sound computer science principles, it seems prudent to spend some time talking about those principles. The first principle of a modularity framework is to attempt to try to focus on one logical piece of functionality at a time, also known as abstraction. With abstraction you are attempting to reduce and factor out details not immediately related to the functionality that you are attempting to code. In modularity, you are concerned with factoring out the control from your test cases. By control, I mean the case flow and functionality in the application-under-test. Contrast that to a data-driven framework (the next framework covered in this series) where you are more concerned with abstracting the data from your test cases. Control abstraction is the abstraction of actions.
The second principle of a modularity framework is closely related to abstraction. It’s the principle of encapsulation. In encapsulation you are concerned with enclosing programming elements (scripts, classes, and so on) inside a larger, more abstract entity (other scripts and classes). Encapsulation teaches you to group related tasks and operations together. It also teaches you to protect your scripts from program changes by providing a stable interface that will shield the actual actions your scripts perform from the way that you call those actions. This is a type of information hiding where you will work to protect your test scripts; having them all call the same set of scripts and classes so that when you need to make changes, those changes cascade to all of your test scripts.
The third principle of a modularity framework is the principle of separation of concerns. Separation of concerns is the process of breaking a program into distinct features that overlap in functionality as little as possible. This principle will guide you as you perform encapsulation. Typically, you will use the separation of concerns principle when you plan how you will divide and structure your test code for application-under-test features and behaviors.
Of the three frameworks in the series, modularity should be the simplest to grasp and master. The three principles described above are well-known programming strategies, and most all developers today perform them without even actively thinking about them. The goal in a modularity framework is to apply these principles in order to improve the maintainability and scalability of your automated test suites. Let’s look at a couple of simple examples of modularity and then we’ll come back and talk about the advantages and disadvantages to this type of approach.
Using a class to implement modularity
For this example we will use a test for www.BookPool.com. The recorded script shown in Listing 1 starts the browser to BookPool.com, searches for books on software testing, and validates three of the books that should be returned on the first results page.
Listing 1. Recorded BookPool.com search script
package tests;
import resources.tests.bookpool_search_recordHelper;
import com.rational.test.ft.*;
import com.rational.test.ft.object.interfaces.*;
import com.rational.test.ft.script.*;
import com.rational.test.ft.value.*;
import com.rational.test.ft.vp.*;
public class bookpool_search_record extends
bookpool_search_recordHelper
{
public void testMain(Object[] args)
{
//Start the browser and load BookPool.com
startApp("www.BookPool.com");
//Search for 'software testing'
text_qs().click(atPoint(74,7));
browser_htmlBrowser(
document_bookpoolDiscountCompu(),
DEFAULT_FLAGS).inputChars("software testing");
button_search_btnGif().click();
//Verify these three books are returned
//in the first results page
TestingComputerSoftware_textVP().performTest(2.0, 20.0);
LessonsLearnedInSoftwareTestinVP().performTest(2.0, 20.0);
HowToBreakSoftware_textVP().performTest(2.0, 20.0);
//Exit
browser_htmlBrowser(document_bookpoolDiscountCompu(),
MAY_EXIT).close();
}
}
|
When I look at this test script, I see something that I know I will want to be able to do in many other scripts – perform a search (Figure 1). There are three lines of code for the search: a click on the qs field, an inputChars for our search criteria, and a click on the search button.
Figure 1. BookPool.com search form
Knowing it’s possible that the developers might someday change the name of the qs field, or that they might use a different search button gif file, you should create a module to do this work for you. That way if you ever have to change the way you search, you can change it in one place and it gets fixed in all your test scripts that call that module. In addition, you reduce the number of lines to perform a search from three to one in all you test scripts.
Listing 2 shows the code for the search module.
Listing 2. BookPool.com search class
package classes;
import classes.bookpoolHelper;
public class bookpool_search extends bookpoolHelper
{
public void performSearch(String criteria){
text_qs().click(atPoint(74,7));
browser_htmlBrowser(document_bookpoolDiscountCompu(),
DEFAULT_FLAGS).inputChars(criteria);
button_search_btnGif().click();
}
}
|
Listing 3 shows the new test script:
Listing 3. Modularity BookPool.com search script
Package tests;
import classes.*;
import com.rational.test.ft.*;
import com.rational.test.ft.object.interfaces.*;
import com.rational.test.ft.script.*;
import com.rational.test.ft.value.*;
import com.rational.test.ft.vp.*;
public class bookpool_search_modularity extends bookpoolHelper
{
public void testMain(Object[] args)
{
//Start the browser and load BookPool.com
startApp("www.BookPool.com");
//Search for 'software testing'
new bookpool_search().performSearch("software testing");
//Verify these three books are returned in
//the first results page
TestingComputerSoftware_textVP().performTest(2.0, 20.0);
LessonsLearnedInSoftwareTestinVP().performTest(2.0, 20.0);
HowToBreakSoftware_textVP().performTest(2.0, 20.0);
//Exit
browser_htmlBrowser(document_bookpoolDiscountCompu(),
MAY_EXIT).close();
}
}
|
It’s not exactly as simple as creating a search class and copying and pasting the search code (as the code above implies). I also had to change my helper class for the script. If you notice in Listing 3, there’s now a bookpoolHelper class instead of the original bookpool_search_recordHelper class. That step was a simple copy and paste. All I had to change in the new bookpoolHelper class was the name of the class. If you’re not familiar with the helper class concept in IBM® Rational® Functional Tester, spend some time in the online help getting familiar with them. You will need to understand them for everything but the most trivial of tasks.
To continue this example, you can now add classes as you desire. You might add classes for processing the BookPool.com navigation tree on the left side of the home page, for processing the shopping cart and checking out, or even simply for reading the source HTML. All of these classes could then be used in any script. All you would have to do is to be sure to keep the bookpoolHelper class up to date as you add more test case scripts and cover more application functionality.
Using multiple scripts to implement modularity
For this example, you will use a test for www.Edwise.org. The recorded script shown in Listing 4 starts the browser to the Edwise.org college budget calculator, enters annual income, and then enters expenses for each category using the worksheets provided. For each calculated total on the page a verification point is recorded to ensure correct calculations.
Listing 4. Recorded Edwise.org script
Package tests;
import resources.tests.edwise_recordHelper;
import com.rational.test.ft.*;
import com.rational.test.ft.object.interfaces.*;
import com.rational.test.ft.script.*;
import com.rational.test.ft.value.*;
import com.rational.test.ft.vp.*;
public class edwise_record extends edwise_recordHelper
{
public void testMain(Object[] args)
{
//Start Student Calculator
startApp("http://www.edwise.org/edwise/edFundFrame.html");
//EdWise - Student Calculator Home Page
image_next().click();
//Edwise - Enter Name
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("Mike");
image_next2().click();
//Edwise - Calculate Budget
//Annual Income
list_incomeBy().click();
list_incomeBy().click(atText("Annually"));
list_aidBy().click();
list_aidBy().click(atText("Annually"));
text_jobEarnings().click(atPoint(75,8));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("15000");
text_monFromParent().click(atPoint(50,10));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("5000");
text_savings().click(atPoint(35,14));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("5000");
text_scholarships().click(atPoint(50,11));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("15000");
text_loans().click(atPoint(40,20));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("15000");
text_totalIncome().click(atPoint(63,15));
//Verify total amount
totalIncome_textVP().performTest(2.0, 20.0);
//Education-Related Expenses
list_expenseBy().click();
list_expenseBy().click(atText("Annually"));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("25000");
text_books().click(atPoint(69,13));
image_edw_detailsGif().click();
//Books/Supplies/Computer Worksheet
text_books2().click(atPoint(42,11));
browser_htmlBrowser(document_books_mod3Html(),
DEFAULT_FLAGS).inputChars("700");
text_supplies().click(atPoint(27,9));
browser_htmlBrowser(document_books_mod3Html(),
DEFAULT_FLAGS).inputChars("200");
text_computer().click(atPoint(24,10));
browser_htmlBrowser(document_books_mod3Html(),
DEFAULT_FLAGS).inputChars("2000");
text_miscSupplies().click(atPoint(24,4));
browser_htmlBrowser(document_books_mod3Html(),
DEFAULT_FLAGS).inputChars("200");
button__OKButton().click();
//Verify total amount
books_textVP().performTest(2.0, 20.0);
//Rent/Mortgage Worksheet
text_rent().click(atPoint(76,11));
image_edw_detailsGif2().click();
text_rent2().click(atPoint(61,11));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("400");
text_tax().click(atPoint(39,18));
text_ins().click(atPoint(31,12));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("100");
text_misc().click(atPoint(25,8));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("50");
button__OKButton2().click();
//Verify total amount
rent_textVP().performTest(2.0, 20.0);
//Utilities Worksheet
text_utilities().click(atPoint(51,8));
image_edw_detailsGif3().click();
text_gas().click(atPoint(24,13));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("67");
text_water().click(atPoint(24,15));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("34");
text_trash().click(atPoint(16,15));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("30");
text_tv().click(atPoint(18,9));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("49");
text_phone().click(atPoint(16,12));
text_cellPhone().click(atPoint(16,18));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("87");
text_internet().click(atPoint(22,11));
text_misc2().click(atPoint(16,12));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("20");
button__OKButton3().click();
//Verify total amount
utilities_textVP().performTest(2.0, 20.0);
//etc...
}
}
|
I didn’t include the entire script due to its length. That’s the problem. A script like this is really long. That makes it undesirable due to maintenance issues. If the application you are testing ever changes, you have to sort through all this code (and all the code in all the other scripts you use to test it) to find and fix problems. What’s nice about a script like this is that it’s naturally broken up into individual logical units. Remember that separation of concerns thing mentioned earlier? You can view each expense line item and its associated worksheet as an independent module, as illustrated in Figures 2 and 3. If you do that, you can record a script for each piece, and then put them all together in your actual test script.
Figure 2. Edwise.org monthly expenses
Figure 3. Edwise.org Rent/Mortgage Worksheet
For example, look at the code shown in Listing 5 for a script that simply processes the Rent/Mortgage Worksheet shown in Figure 3.
Listing 5. Rent/Mortgage Worksheet module script
Package scripts;
import resources.scripts.rent_mortgage_worksheet_moduleHelper;
import com.rational.test.ft.*;
import com.rational.test.ft.object.interfaces.*;
import com.rational.test.ft.script.*;
import com.rational.test.ft.value.*;
import com.rational.test.ft.vp.*;
public class rent_mortgage_worksheet_module extends
rent_mortgage_worksheet_moduleHelper
{
public void testMain(Object[] args)
{
//Rent/Mortgage Worksheet
text_rent().click(atPoint(76,11));
image_edw_detailsGif2().click();
text_rent2().click(atPoint(61,11));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("400");
text_tax().click(atPoint(39,18));
text_ins().click(atPoint(31,12));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("100");
text_misc().click(atPoint(25,8));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("50");
button__OKButton2().click();
//Verify total amount
rent_textVP().performTest(2.0, 20.0);
}
}
|
If you do this to each worksheet item your test case script looks similar to Listing 6.
Listing 6. Modularity Edwise.org script
Package tests;
import resources.tests.edwise_modularityHelper;
import com.rational.test.ft.*;
import com.rational.test.ft.object.interfaces.*;
import com.rational.test.ft.script.*;
import com.rational.test.ft.value.*;
import com.rational.test.ft.vp.*;
public class edwise_modularity extends edwise_modularityHelper
{
public void testMain(Object[] args)
{
//Start Student Calculator
startApp("http://www.edwise.org/edwise/edFundFrame.html");
//EdWise - Student Calculator Home Page
image_next().click();
//Edwise - Enter Name
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("Mike");
image_next2().click();
//Edwise - Calculate Budget
//Annual Income
callScript("scripts.annual_income_module");
//Education-Related Expenses
callScript("scripts.education_related_worksheet_module");
//Rent/Mortgage Worksheet
callScript("scripts.rent_mortgage_worksheet_module");
//Utilities Worksheet
callScript("scripts.utilities_worksheet_module");
//Food/Household Worksheet
callScript("scripts.food_household_worksheet_module");
//Student Loan Payment Worksheet
callScript("scripts.student_loan_payment_worksheet_module");
//Credit Card/Personal Loan Worksheet
callScript("scripts.credit_loans_worksheet_module");
//Transportation Costs Worksheet
callScript("scripts.transportation_costs_worksheet_module");
//Clothing Costs Worksheet
callScript("scripts.clothing_costs_worksheet_module");
//Additional Expenses Worksheet
callScript("scripts.additional_expenses_worksheet_module");
//Verify Total Annual Expenses
totalExpense_textVP().performTest(2.0, 20.0);
//Verify Summary amounts
summary_gridVP().performTest(2.0, 20.0);
//Click Next
image_next3().click();
//etc...
}
}
|
By using the callScript command you can call the modularity scripts you created from your test case script. This makes the code more readable and easier to maintain. What’s really nice about this method is that you can use modular scripts like these as building blocks to create different test case scenarios. For each modular script you have above, you might have three (or more) different cost classifications: cheap, average, expensive. You could then mix and match them to test for different scenarios like cheap rent with a high cost of utilities, or low transportation costs with a credit card debt.
Of the three types of frameworks this series will cover (modularity, data-driven, and keyword driven), a modularity framework is the simplest to understand and to implement. It’s also the easiest to incorporate with the other frameworks -- putting modularity and data-driven together for example. In general, there is a low level of technical knowledge required to implement modularity in Rational Functional Tester. All you really need to learn is how to manage the helper classes -- something that you really should understand if you are using Rational Functional Tester in any capacity (including record and playback).
The main advantage of modularity is reuse and the reduced maintenance costs that come with reuse. Compared to record and playback, modularity should result in more readable scripts that are easier to read and debug. There is typically a higher cost to create test modular scripts then record and playback scripts, but the maintenance costs are cheaper. This means you need to understand how long you will be using your test scripts to fully understand the costs of implementing modularity. Cost-benefit analysis might help you decide if modularity is appropriate for your current project. Odds are, even if your entire framework isn’t modular, you will end up identifying core functionality to move to helper classes or helper scripts.
Script modularity is not without its problems. Modularity results in dependent scripts, which can result in blocking bugs. A blocking bug is a defect or issue that is preventing you from doing something else until it gets fixed. With dependent test scripts, script bottlenecks can occur. What happens if a module fails due to a screen change, but the majority of the scripts you have use that module? For example, if the script you use to process the shopping cart fails because the developers changed the way items in the shopping cart are displayed, why should your check order status test case care about that? Can't it just click the order button in the shopping cart without validating the table format? That's not what it's testing. With modularity, if the first module fails, all modules following that module will likely fail.
With modularity, application state and data sharing can become an issue. If a module fails, what does that mean for all the modules that rely on it? How do you check order status if an order was never placed? How do you add an item to the shopping cart if you never performed a search? The complexity of sharing data and state can make script debugging more difficult, and it often takes longer to figure out if a script failure is a problem with the test script or the application under test. These problems can make reliability an issue.
Take some time to abstract some of the most commonly used functions in the application you’re testing into scripts or classes. You might also consider doing this for the most dynamic portions of the application. Use these modules in your existing scripts and any new scripts you make. You don’t need to convert all of your scripts at once. That’s one of the cool things about modules; you can use them or not use them as you want. You’re not locked into using them.
Also worth mentioning is the fact that modules can be extended to be used in advanced frameworks, like model-based test frameworks and frameworks for high-volume automated testing. There are a couple of resources in the following list if you’re interested in extending your existing modularity framework. The next article in this series will take a look at implementing a data-driven framework in Rational Functional Tester. Until then, take a look at the resources to gain a fuller understanding of some of the costs and benefits associated with a modularity framework, and some of the ways which you can use modularity for more powerful test automation.
Learn
-
To learn more, visit the Functional Tester product resource area on developerWorks Rational. You'll find technical documentation, how-to articles, education, downloads, product information, and more.
-
Assessing the benefits of independent test scripts versus dependent test scripts: by Michael Kelly
-
Using cost-benefit analysis to compare different test structures for Rational Robot: by Michael Kelly
-
Choosing a test automation framework by Michael Kelly
-
Model-based testing using IBM Rational Functional Tester by Jeff Feldstein
-
High-volume test automation by Kaner, Bond, and McGee
-
Gathering Performance Information While Executing Everyday Automated Tests by Michael Kelly
Get products and technologies
-
Try out Rational products by visiting the trial downloads area.
Discuss
Michael Kelly is currently an independent consultant and provides custom training in the IBM Rational testing tools. He consults, writes, and speaks on topics in software testing. He is currently serving as the Program Director for the Indianapolis Quality Assurance Association and is a Director at Large for the Association for Software Testing. He can be reached by email at Mike@MichaelDKelly.com.
Comments (Undergoing maintenance)





