Skip to main content

Framework automation with IBM Rational Functional Tester: Modularity

Michael Kelly (Mike@MichaelDKelly.com), Consultant, www.MichaelDKelly.com
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.

Summary:  Read the first in a series of articles focusing on creating testing frameworks. The series also covers data-driven, and keyword-driven frameworks.

Date:  21 Oct 2005 (Published 11 Oct 2005)
Level:  Intermediate
Activity:  1029 views

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
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
Edwise.org monthly expenses

Figure 3. Edwise.org Rent/Mortgage Worksheet
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.


Advantages and disadvantages

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.


Next steps

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.


Resources

Learn

Get products and technologies

Discuss

About the author

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)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Rational
ArticleID=94993
ArticleTitle=Framework automation with IBM Rational Functional Tester: Modularity
publish-date=10212005
author1-email=Mike@MichaelDKelly.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers