Saturday, November 13, 2010

Testing sorting feature of an application

All applications display data and allow the user to sort the data on some column. I am sure you would have written or executed test cases to test the sorting feature of your application. There are many strategies to automate such test. In this post I will try to explain a strategy I use widely.

Data is generally displayed in the form of a table (webtable) with each row containing one record. Generally in web applications the sorting feature is given on some or all column of the table. Let’s look at one such website that displays data in this fashion. Navigate to www.vcdq.com


This website as you can see has information on the latest movie releases. It allows you to sort the data on two of the columns, Date and Release. You can sort the data by clicking the respective column header. Both columns can be sorted in ascending and descending order. A small arrow icon is indicating the current sorting order next to the column header text.

My strategy involves the following
a)    Reading all the data of this table into a java ArrayList. Lets call this ExpectedList
b)    Perform the sort on the application by clicking the column header
c)    Read the data from the table again and store it in another ArrayList. Lets call this ActualListAfterSort
d)    Sort the ExpectedList
e)    Compare ExpectedList with ActualListAfterSort and verify that the order of the elements is same.

There is a distinction between the ArrayList that holds primitive datatypes like String or int and the one I use. I use a user defined class to hold the rows of data. Each member element of this class represents one column of the table. The class is defined below.

public class ReleaseInfoTableRecord {
String standard;
Date date;
String format;
String source;
String release;
double imdb;
int disks;
String group;
int ratingAudio;
int ratingVideo;

public ReleaseInfoTableRecord(String standard, Date date, String format,
        String source, String release, double imdb, int disks, String group,
        int ratingAudio, int ratingVideo) {
    super();
    this.standard = standard;
    this.date = date;
    this.format = format;
    this.source = source;
    this.release = release;
    this.imdb = imdb;
    this.disks = disks;
    this.group = group;
    this.ratingAudio = ratingAudio;
    this.ratingVideo = ratingVideo;
}
}

I will insert objects of this class into the ArrayList. An object will represent one row of the table. This is how will insert record within the ArrayList.

ArrayList<ReleaseInfoTableRecord> LatestMovieReleaseDisplayed=new ArrayList<ReleaseInfoTableRecord>();
        
LatestMovieReleaseDisplayed.add(new ReleaseInfoTableRecord(standard, date, format, 
                    source, release, imdb, disks, group, ratingAudio, ratingVideo  ) );

Now in order to sort the data of the ArrayList I would have to define some comparators. I will have to define comparators for all the sort features that I wish to test. For example If the application under test allows sorting the data on ascending and descending order on column Date and I want to test this feature than I need to define two comparators one for ascending and other for descending on date. I will be defining these comparators in the class ReleaseInfoTableRecord.

static final Comparator<ReleaseInfoTableRecord> SORT_DATE_ASCENDING =  new Comparator<ReleaseInfoTableRecord>(){
    public int compare(ReleaseInfoTableRecord o1, ReleaseInfoTableRecord o2) {
        int result = o1.getDate().compareTo(o2.getDate());
        if (o1.getDate().equals(o2.getDate()))
            result =o1.getRelease().compareTo(o2.getRelease())*-1;
        return (result);
    }
};

static final Comparator<ReleaseInfoTableRecord> SORT_DATE_DESCENDING =  new Comparator<ReleaseInfoTableRecord>(){
    public int compare(ReleaseInfoTableRecord o1, ReleaseInfoTableRecord o2) {
        int result = o1.getDate().compareTo(o2.getDate())*-1;
        if (o1.getDate().equals(o2.getDate()))
            result =o1.getRelease().compareTo(o2.getRelease());
        return (result);
    }
};


The hashCode and equals method of the data class must be overridden to make the comparison possible. This overriden methods can be genarated in eclipse by doing right click source-->generate hashcode and equals. You will se this below in the full class code.

If the two records have the same date then the application sorts on the basis of Release name. Note the logic for each comparator when the date is same. This is specific to this application and the application that you are testing may have a different logic. Go thru the application logic carefully before defining the comparators.

Now we define the actual test method that tests the sorting feature. Note that I have read the data into the ArrayList dataAsDisplayedInDefaultView in an earlier step. The code in the while loop is instruction that performs the sorting on the web application i.e clicks the column header link repeatedly till the appropriate arrow icon appears. Your application may involve steps different than this to invoke sort.

@Test
    public void testSortingDateAscending() {            
        int i=0;
        //Click Date column header till the small up arrow is visible next to <Date> column header, 
        //don't get confused by the title of the up arrow image        
        while (false==selenium.isElementPresent("//table//th[2]/a/img[@title='sort descending']")&&i<2){
        selenium.click("link=Date");
        selenium.waitForPageToLoad("30000");
        }        
        
        ArrayList<ReleaseInfoTableRecord> dataAsDisplayedAfterSort=getLatestMovieReleaseDisplayed();
        //make a copy of the arraylist that holds the displayed data 
        ArrayList<ReleaseInfoTableRecord> expectedOrder=dataAsDisplayedInDefaultView;
        //sort the copy on Date Descending, this is how the data is sorted in the default view on this website
        Collections.sort(expectedOrder, ReleaseInfoTableRecord.SORT_DATE_ASCENDING);
        //This will pass only if the elements in both the lists appear in the same order
        org.testng.Assert.assertTrue(dataAsDisplayedAfterSort.equals(expectedOrder));        
    }

Below you can see both the classes in their full glory. ReleaseInfoTableRecord class that will be used to hold the data (record of the table) and SortingTest is the test class.

package test;


import java.util.Comparator;
import java.util.Date;

public class ReleaseInfoTableRecord {
String standard;
Date date;
String format;
String source;
String release;
double imdb;
int disks;
String group;
int ratingAudio;
int ratingVideo;

public ReleaseInfoTableRecord(String standard, Date date, String format,
        String source, String release, double imdb, int disks, String group,
        int ratingAudio, int ratingVideo) {
    super();
    this.standard = standard;
    this.date = date;
    this.format = format;
    this.source = source;
    this.release = release;
    this.imdb = imdb;
    this.disks = disks;
    this.group = group;
    this.ratingAudio = ratingAudio;
    this.ratingVideo = ratingVideo;
}



@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((date == null) ? 0 : date.hashCode());
    result = prime * result + disks;
    result = prime * result + ((format == null) ? 0 : format.hashCode());
    result = prime * result + ((group == null) ? 0 : group.hashCode());
    long temp;
    temp = Double.doubleToLongBits(imdb);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    result = prime * result + ratingAudio;
    result = prime * result + ratingVideo;
    result = prime * result + ((release == null) ? 0 : release.hashCode());
    result = prime * result + ((source == null) ? 0 : source.hashCode());
    result = prime * result + ((standard == null) ? 0 : standard.hashCode());
    return result;
}
@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    ReleaseInfoTableRecord other = (ReleaseInfoTableRecord) obj;
    if (date == null) {
        if (other.date != null)
            return false;
    } else if (!date.equals(other.date))
        return false;
    if (disks != other.disks)
        return false;
    if (format == null) {
        if (other.format != null)
            return false;
    } else if (!format.equals(other.format))
        return false;
    if (group == null) {
        if (other.group != null)
            return false;
    } else if (!group.equals(other.group))
        return false;
    if (Double.doubleToLongBits(imdb) != Double.doubleToLongBits(other.imdb))
        return false;
    if (ratingAudio != other.ratingAudio)
        return false;
    if (ratingVideo != other.ratingVideo)
        return false;
    if (release == null) {
        if (other.release != null)
            return false;
    } else if (!release.equals(other.release))
        return false;
    if (source == null) {
        if (other.source != null)
            return false;
    } else if (!source.equals(other.source))
        return false;
    if (standard == null) {
        if (other.standard != null)
            return false;
    } else if (!standard.equals(other.standard))
        return false;
    return true;
}
public String getStandard() {
    return standard;
}
public void setStandard(String standard) {
    this.standard = standard;
}
public Date getDate() {
    return date;
}
public void setDate(Date date) {
    this.date = date;
}
public String getFormat() {
    return format;
}
public void setFormat(String format) {
    this.format = format;
}
public String getSource() {
    return source;
}
public void setSource(String source) {
    this.source = source;
}
public String getRelease() {
    return release;
}
public void setRelease(String release) {
    this.release = release;
}
public double getImdb() {
    return imdb;
}
public void setImdb(double imdb) {
    this.imdb = imdb;
}
public int getDisks() {
    return disks;
}
public void setDisks(int disks) {
    this.disks = disks;
}
public String getGroup() {
    return group;
}
public void setGroup(String group) {
    this.group = group;
}
public int getRatingAudio() {
    return ratingAudio;
}
public void setRatingAudio(int ratingAudio) {
    this.ratingAudio = ratingAudio;
}
public int getRatingVideo() {
    return ratingVideo;
}
public void setRatingVideo(int ratingVideo) {
    this.ratingVideo = ratingVideo;
}

static final Comparator<ReleaseInfoTableRecord> SORT_DATE_ASCENDING =  new Comparator<ReleaseInfoTableRecord>(){
    public int compare(ReleaseInfoTableRecord o1, ReleaseInfoTableRecord o2) {
        int result = o1.getDate().compareTo(o2.getDate());
        if (o1.getDate().equals(o2.getDate()))
            result =o1.getRelease().compareTo(o2.getRelease())*-1;
        return (result);
    }
};

static final Comparator<ReleaseInfoTableRecord> SORT_DATE_DESCENDING =  new Comparator<ReleaseInfoTableRecord>(){
    public int compare(ReleaseInfoTableRecord o1, ReleaseInfoTableRecord o2) {
        int result = o1.getDate().compareTo(o2.getDate())*-1;
        if (o1.getDate().equals(o2.getDate()))
            result =o1.getRelease().compareTo(o2.getRelease());
        return (result);
    }
};


static final Comparator<ReleaseInfoTableRecord> SORT_RELEASE_ASCENDING =  new Comparator<ReleaseInfoTableRecord>(){
    public int compare(ReleaseInfoTableRecord o1, ReleaseInfoTableRecord o2) {
        int result = o1.getRelease().compareTo(o2.getRelease());
        return (result);
    }
};

static final Comparator<ReleaseInfoTableRecord> SORT_RELEASE_DESCENDING =  new Comparator<ReleaseInfoTableRecord>(){
    public int compare(ReleaseInfoTableRecord o1, ReleaseInfoTableRecord o2) {
        int result = o1.getRelease().compareTo(o2.getRelease());
        return (result * -1);
    }
};

}


package test;


import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;

import org.openqa.selenium.server.SeleniumServer;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import com.thoughtworks.selenium.SeleneseTestCase;

public class SortingTest extends SeleneseTestCase{
    
    ArrayList<ReleaseInfoTableRecord> dataAsDisplayedInDefaultView;

     @BeforeClass
        public void setUp() throws Exception {
            SeleniumServer seleniumserver=new SeleniumServer();
            seleniumserver.boot();
            seleniumserver.start();
            setUp("http://www.vcdq.com", "*firefox");
            selenium.open("/");
            selenium.windowMaximize();
            selenium.windowFocus();
            
            //filter the results to reduce the data-set to manageable levels            selenium.select("filterStandard", "label=SCENE");
            selenium.select("filterTerm3", "label=DVDR");
            selenium.select("filterTerm2", "label=DVD");
            selenium.click("//input[@value='Filter']");
            selenium.waitForPageToLoad("20000");
            dataAsDisplayedInDefaultView=getLatestMovieReleaseDisplayed();
        }    
    
    @Test
    public void testDefaultSorting(){                
        //make a copy of the arraylist that holds the displayed data 
        ArrayList<ReleaseInfoTableRecord> expectedOrder=dataAsDisplayedInDefaultView;
        //sort the copy on Date Descending, this is how the data is sorted in the default view on this website
        Collections.sort(expectedOrder, ReleaseInfoTableRecord.SORT_DATE_DESCENDING);
        //This will pass only if the elements in both the lists appear in the same order
        org.testng.Assert.assertTrue(dataAsDisplayedInDefaultView.equals(expectedOrder));        
    }
    
        
    @Test
    public void testSortingDateAscending() {            
        int i=0;
        //Click Date column header till the small up arrow is visible next to <Date> column header, 
        //don't get confused by the title of the up arrow image
        while (false==selenium.isElementPresent("//table//th[2]/a/img[@title='sort descending']")&&i<2){
        selenium.click("link=Date");
        selenium.waitForPageToLoad("30000");
        }        
        
        ArrayList<ReleaseInfoTableRecord> dataAsDisplayedAfterSort=getLatestMovieReleaseDisplayed();
        //make a copy of the arraylist that holds the displayed data 
        ArrayList<ReleaseInfoTableRecord> expectedOrder=dataAsDisplayedInDefaultView;
        //sort the copy on Date Descending, this is how the data is sorted in the default view on this website
        Collections.sort(expectedOrder, ReleaseInfoTableRecord.SORT_DATE_ASCENDING);
        //This will pass only if the elements in both the lists appear in the same order
        org.testng.Assert.assertTrue(dataAsDisplayedAfterSort.equals(expectedOrder));        
    }
    
    @Test
    public void testSortingReleaseNameAscending(){
        int i=0;
        //Click Date column header till the small up arrow is visible next to <Release> column header, 
        //don't get confused by the title of the up arrow image
        while (false==selenium.isElementPresent("//table//th[6]/a/img[@title='sort descending']")&&i<2){
        //Click Date column header once so that the sort ascending image is visible
        selenium.click("link=Release");
        selenium.waitForPageToLoad("30000");
        i=i+1;
        }
        
        ArrayList<ReleaseInfoTableRecord> dataAsDisplayedAfterSort=getLatestMovieReleaseDisplayed();
        //make a copy of the arraylist that holds the displayed data 
        ArrayList<ReleaseInfoTableRecord> expectedOrder=dataAsDisplayedInDefaultView;
        //sort the copy on Date Descending, this is how the data is sorted in the default view on this website
        Collections.sort(expectedOrder, ReleaseInfoTableRecord.SORT_RELEASE_ASCENDING);
        //This will pass only if the elements in both the lists appear in the same order
        org.testng.Assert.assertTrue(dataAsDisplayedAfterSort.equals(expectedOrder));        
    }
    
    @Test
    public void testSortingReleaseNameDescending(){            
        int i=0;
        //Click Date column header till the small up arrow is visible next to <Release> column header, 
        //don't get confused by the title of the up arrow image        
        while (false==selenium.isElementPresent("//table//th[6]/a/img[@title='sort ascending']")&&i<2) {
        //Click Date column header once so that the sort ascending image is visible
        selenium.click("link=Release");
        selenium.waitForPageToLoad("30000");        
        }
        
        ArrayList<ReleaseInfoTableRecord> dataAsDisplayedAfterSort=getLatestMovieReleaseDisplayed();
        //make a copy of the arraylist that holds the displayed data 
        ArrayList<ReleaseInfoTableRecord> expectedOrder=dataAsDisplayedInDefaultView;
        //sort the copy on Date Descending, this is how the data is sorted in the default view on this website
        Collections.sort(expectedOrder, ReleaseInfoTableRecord.SORT_RELEASE_DESCENDING);
        //This will pass only if the elements in both the lists appear in the same order
        org.testng.Assert.assertTrue(dataAsDisplayedAfterSort.equals(expectedOrder));        
    }
    
    public ArrayList<ReleaseInfoTableRecord> getLatestMovieReleaseDisplayed() {
        ArrayList<ReleaseInfoTableRecord> LatestMovieReleaseDisplayed=new ArrayList<ReleaseInfoTableRecord>();
        
        String standard;
        Date date=null;
        String format;
        String source;
        String release;
        double imdb;
        int disks;
        String group;
        int ratingAudio;
        int ratingVideo;
        
        String rating;
        String imdbCellContent;
        int tableRowCount=selenium.getXpathCount("/descendant::table//tr").intValue();
        
        //read the data from the webtable
        for (int i=1;i<tableRowCount;i++){
            standard=selenium.getText("XPATH=/descendant::table//tr["+i+"]//td[1]");
            DateFormat df = new SimpleDateFormat("MM/dd/yyyy");
            try{
            date=df.parse(selenium.getText("XPATH=/descendant::table//tr["+i+"]//td[2]"));
            }catch (ParseException p){
                System.out.println(p.getMessage());
            }
            
            format=selenium.getText("XPATH=/descendant::table//tr["+i+"]//td[4]");
            source=selenium.getText("XPATH=/descendant::table//tr["+i+"]//td[5]");
            release=selenium.getText("XPATH=/descendant::table//tr["+i+"]//td[6]");
            imdbCellContent=selenium.getText("XPATH=/descendant::table//tr["+i+"]//td[10]");            
            imdb=!(imdbCellContent.equalsIgnoreCase("N/A"))?Double.parseDouble(selenium.getText("XPATH=/descendant::table//tr["+i+"]//td[10]")):0.0;
            disks=Integer.parseInt(selenium.getText("XPATH=/descendant::table//tr["+i+"]//td[11]"));
            group=selenium.getText("XPATH=/descendant::table//tr["+i+"]//td[12]");
            rating=selenium.getText("XPATH=/descendant::table//tr["+i+"]//td[13]");
            ratingVideo=Integer.parseInt(rating.split(" ")[0].split(":")[1]);
            ratingAudio=Integer.parseInt(rating.split(" ")[1].split(":")[1]);
            
            //insert the data as a instances of the class ReleaseInfoTableRecord into the arraylist
            LatestMovieReleaseDisplayed.add(new ReleaseInfoTableRecord(standard, date, format, 
                    source, release, imdb, disks, group, ratingAudio, ratingVideo  ) );
    }
return LatestMovieReleaseDisplayed;
}
    
}

Copy paste these classes into your project and run as testng test to see them in action.

This is the most robust and foolproof strategy I have come across for testing sorting functionality. I use it to test search results, reports etc. You might also want to create test data before invoking the sort i.e create test data items abc, efg, zab or you could just use the existing data in the system.

8 Comments:

bob said...

Mahesh,
This is exactly what I'm looking for. I'm trying to follow what you did but it looks like the site has changed since you posted this and I can't quite work out some of the details. Can you explain the section where the comment starts:

//filter the results to reduce the data-set to manageable levels

I may be able to work out the details with this bit but it would be best if you could update such that your code will work with the site as is.

This is an excellent post. Well done!

Nagaraj Hebbar said...

Hi Mahesh,
I just wanted to know how to handle the Ext-JS pages by using the selenium. This is am trying from couple of days before,my test case is getting failed because of random ID generation in Ext-JS pages. Can you please explain me how to handle this case
contact me on nagaraja12hebbar@gmail.com

Regards
Nagaraj

James said...

It seems to be a useful information and I am sure, there will be many out there who will prefer implement automation testing in the same way like you did.

dave said...

Read all the related Posts:

64 Software Manual Testing Interview Questions

Answers To Common Job Interview Questions

Behavioral Questions In Interviews

Questions to Ask at an Interview

Competency based Interview Questions


Read all the related Posts:

How to avoid missing defect in Software Testing?

Defect Management Process

What is the difference between a Test Strategy and Test Plan?

Beginners Guide to ETL Testing

Step by step guide from Test Case Development to Test Execution

Anonymous said...

Read all the related Posts:

64 Software Manual Testing Interview Questions

Answers To Common Job Interview Questions

Behavioral Questions In Interviews

Questions to Ask at an Interview

Competency based Interview Questions


Read all the related Posts:

How to avoid missing defect in Software Testing?

Defect Management Process

What is the difference between a Test Strategy and Test Plan?

Beginners Guide to ETL Testing

Step by step guide from Test Case Development to Test Execution

Anonymous said...

Read all the related Posts:


Basic of VBScript Language for QTP

Introduction to QTP (QuickTest Professional) Part2

Introduction to QTP (QuickTest Professional) Part3

Introduction to QTP (QuickTest Professional) Part4

Basic of VBScript Language for QTP


Read all the related Posts:

64 Software Manual Testing Interview Questions

Answers To Common Job Interview Questions

Behavioral Questions In Interviews

Questions to Ask at an Interview

Competency based Interview Questions

celina jones said...

Very informative post thanks for share this with us i highly appreciate you for this information thanks once again for sharing information like this Very informative post Thanks for sharing with you, I appreciate this information thank you again for sharing information like this!.............:)
Automated System

GE Fanuc plc Software Free Download said...

I was very encouraged to find this site. GE Fanuc plc Software Free Download