Overview:
As a software engineer, We all face some errors/exceptions while writing code! So what do we do when we face such a problem? If we are not sure, We google for solutions immediately. Don’t we? We google because we know that we would not be alone and someone would have already found the solution, for the problem we are facing now, which we could use to solve our problem.
Well, What to do if we face an issue in the high level software design – when connecting different classes / modules – when you have only a vague idea!! How can we google such things when we ourselves are not sure of the problem we have in the first place!!
No worries, You still have a solution!
Design patterns are the solutions for the problems which you might face in the software design!!
Design Patterns are well optimized and reusable solutions to a given problem in the software design. It helps us to show the relationship among the classes and the way in which they interact. Design pattern is a template which you have to carefully analyze and use it in appropriate places.
More information on design pattern can be found here.
Design Patterns in Test Automation:
As an automated test engineer, should we really care about design principles and patterns? Why do we need to learn/use Design Patterns in functional test automation?
After all, Test automation framework/automated test case is also a software which is used to test another software. So, we could apply the same design principles/patterns in our test automation design as well to come up with more elegant solution for any given design related problem in the test automation framework.
Remember that Design Patterns are NOT really mandatory. So you do not have to use them! But when you learn them, you would know exactly when to use! Basically these are all well tested solutions. So when you come across a problem while designing your framework/automated test cases, these design patterns could immediately help you by proving you a formula / template & saves us a lot of time and effort.
Note: Your aim should not be to implement a certain pattern in your framework. Instead, identify a problem in the framework and then recognize the pattern which could be used to solve the problem. Do not use any design pattern where it is not really required!!
In this article, We are going to see where we could use the Template Method Pattern which is one of the Behavioral Patterns.
Udemy – Selenium WebDriver and Design Patterns Course:
VinsGuru has released a brand new course in Udemy on Selenium WebDriver and Design Patterns. ~8 hours course with all the important design patterns to design better, reusable page object models, test classes etc. Please access the above link which gives you the special discount. You can also get your money back if you do not like the course within 30 days.
Template Method Pattern:
Goal: Define the skeleton of an algorithm in an operation, deferring some steps to client sub classes. Template Method lets sub classes redefine certain steps of an algorithm without changing the algorithm’s structure.
If we consider this below example, all the workers have same routine. getup, eat breakfast and so on. But type of work could be different for each worker. [source: https://sourcemaking.com/design_patterns/template_method]
Lets see where we could use design pattern in Test automation.
Template Method Pattern in Test Automation:
Lets consider this – PHPTravels – application. Lets also assume that we need to automate this application’s Hotel and Car reservation workflow. As part of the business workflow, we need to below steps.
- enter the locaiton
- enter the dates
- click on the search
- sort the results by the price
- pick the lowest price
- pay
- get the confirmation number.
The above steps are applicable for both hotel and car reservations workflow. They have significant similarities. However screens are different, car reservation might require 2 locations one for pickup and one for dropoff which is different from just 1 location for Hotel.
Considering the workflow, We could create a template like this.
public abstract class Reservation {
abstract void setLocation();
abstract void setFromDate();
abstract void setTillDate();
abstract void search();
abstract void sortByPrice();
abstract void chooseFirst();
abstract void pay();
abstract void printConfirmationNumber();
public final void reserve() {
setLocation();
setFromDate();
setTillDate();
search();
sortByPrice();
chooseFirst();
pay();
printConfirmationNumber();
}
}
Hotel Page Object:
public class HotelPage {
@FindBy(id = "citiesInput")
private WebElement city;
@FindBy(name = "checkin")
private WebElement checkin;
@FindBy(name = "checkout")
private WebElement checkout;
@FindBy(css = "input[type=submit]")
private WebElement searchButton;
public void setCity(String location) {
this.city.sendKeys(location);
}
public void setCheckInDate(String date) {
this.checkin.sendKeys(date);
}
public void setCheckOutDate(String date) {
this.checkout.sendKeys(date);
}
public void search() {
this.searchButton.click();
}
}
Car Page Object:
public class CarPage {
@FindBy(name = "startlocation")
private WebElement pickupCity;
@FindBy(name = "endlocation")
private WebElement dropoffCity;
@FindBy(name = "pickupdate")
private WebElement pickupDate;
@FindBy(name = "dropoffdate")
private WebElement dropoffDate;
@FindBy(css = "input[type=submit]")
private WebElement searchButton;
public void setPickupCity(String location) {
this.pickupCity.sendKeys(location);
}
public void setDropoffCity(String location) {
this.dropoffCity.sendKeys(location);
}
public void setPickupDate(String date) {
this.pickupDate.sendKeys(date);
}
public void setDropoffDate(String date) {
this.dropoffDate.sendKeys(date);
}
public void search() {
this.searchButton.click();
}
}
Hotel Reservation Workflow:
public class HotelReservation extends Reservation {
private final HotelPage hotel;
private final String city;
private final String checkin;
private final String checkout;
public HotelReservation(WebDriver driver, String city, String checkin, String checkout) {
this.hotel = PageFactory.initElements(driver, HotelPage.class);
this.city = city;
this.checkin = checkin;
this.checkout = checkout;
this.goTo(driver);
}
private void goTo(WebDriver driver) {
driver.get("http://www.phptravels.net/");
}
@Override
void setLocation() {
hotel.setCity(city);
}
@Override
void setFromDate() {
hotel.setCheckInDate(checkin);
}
@Override
void setTillDate() {
hotel.setCheckOutDate(checkout);
}
@Override
void search() {
hotel.search();
}
@Override
void sortByPrice() {
// TODO Auto-generated method stub
}
@Override
void chooseFirst() {
// TODO Auto-generated method stub
}
@Override
void pay() {
// TODO Auto-generated method stub
}
@Override
void printConfirmationNumber() {
// TODO Auto-generated method stub
}
}
Car Reservation Workflow:
public class CarReservation extends Reservation {
private final CarPage car;
private final String pickup;
private final String dropoff;
private final String pickupDate;
private final String dropoffDate;
public CarReservation(WebDriver driver, String pickup, String dropoff, String pickupdate, String dropoffdate) {
this.car = PageFactory.initElements(driver, CarPage.class);
this.pickup = pickup;
this.dropoff = dropoff;
this.pickupDate = pickupdate;
this.dropoffDate = dropoffdate;
this.goTo(driver);
}
private void goTo(WebDriver driver) {
driver.get("http://www.phptravels.net/");
driver.findElement(By.cssSelector("a[href=\"#CARTRAWLER\"]")).click();
}
@Override
void setLocation() {
car.setPickupCity(pickup);
car.setDropoffCity(dropoff);
}
@Override
void setFromDate() {
car.setPickupDate(pickupDate);
}
@Override
void setTillDate() {
car.setDropoffDate(dropoffDate);
}
@Override
void search() {
car.search();
}
@Override
void sortByPrice() {
// TODO Auto-generated method stub
}
@Override
void chooseFirst() {
// TODO Auto-generated method stub
}
@Override
void pay() {
// TODO Auto-generated method stub
}
@Override
void printConfirmationNumber() {
// TODO Auto-generated method stub
}
}
TestNG Test:
public class TemplateMethodPatternTest {
private WebDriver driver;
@Test(dataProvider = "reservations")
public void reservationTest(Reservation r) {
r.reserve();
}
@DataProvider
public Object[][] reservations() {
return new Object[][] {
new Object[] {
new HotelReservation(driver, "San Antonio", "04/30/2017", "05/02/2017")
},
new Object[] {
new CarReservation(driver, "San Antonio", "San Antonio", "04/30/2017", "05/02/2017")
},
new Object[] {
new HotelReservation(driver, "Houston", "05/02/2017", "05/05/2017")
},
new Object[] {
new CarReservation(driver, "Houston", "Austin", "05/02/2017", "05/05/2017")
}
};
}
@BeforeTest
public void beforeTest() {
driver = new FirefoxDriver();
}
@AfterTest
public void afterTest() {
driver.quit();
}
}
Summary:
By using Template Method Pattern, we create a skeleton for the reservation functionality. If there is any common functionality, then it can be kept in the Template class itself. Hotel, Car, Flight etc any reservation can implement this template by overriding abstract methods of the Template. So, there is no code duplication.
Design Pattern is a very good tool for effective communication. How? As soon as we name a pattern, Others can immediately picture the high-level-design in their heads & get the solution for the given problem. Thus, Design Pattern enables us to explain the design in a much better way.
You might be interested in other design patterns as well. Check below articles to learn more!
Happy Testing & Subscribe ?
I am unable to run two different test as per the implementation. For example if I have CarReservation and HotelReservation, the first carReservation will work and HotelReservation will fail as there it was unable to open a second browser to run this test
Why not? We open and close the browser only once. Each Reservation class has the goto method to take the user to the starting point.
Yes. It should work like that. But don’t know why it is not happening. As per the implementation for both test run browser should open and close. Am I missing something? I did everything like this