Skip to content

How To Use Design Patterns In Test Automation

Design pattern

Design patterns are design-level solutions for recurring problems and are used extensively in programming. Design patterns are like rules and guidelines of best practices as they provide the concept to tackle the problems and design a solution.

Although the use of design patterns is optional, However, the knowledge of them and how to use them is like the cherry on the cake. In this article, I will discuss how we can use design patterns in test automation frameworks.

Advantages of Design Patterns in Test Automation:

  • Helps in code reusability.
  • More flexible and maintainable code.
  • Helps in enhancing reliability.

Types of Design Patterns:

1)Creational Design Patterns: These patterns are designed for class instantiation(Object creation mechanism). Which increases the flexibility and reusability of the code. ex: Singelton pattern, Builder pattern, Factory design pattern.

A)Singelton Pattern: The Singelton pattern ensures a class has only one instance and provides a global access point. This pattern is mainly used for loggers, connections, or external resources. It is very useful when we need to use the same object across the whole framework. Creating a singleton class consist of:

a)Making a private constructor
b)Make a static reference to the class
c)Make a static method that returns an object of type class and should also check whether the class is already instantiated or not.

We will create a singleton class to load test data from the config file.


import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

public class SingletonExample {
    private static SingletonExample singletonExampleInstance = null;
    private BufferedReader bufferedReader = null;
    private SingletonExample(){
        try {
            File file = new File(System.getProperty("user.dir") + "/config.properties");
            bufferedReader = new BufferedReader(new FileReader(file));
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }

    public synchronized static SingletonExample getInstance(){
        if(singletonExampleInstance == null){
            singletonExampleInstance = new SingletonExample();
        }
        return singletonExampleInstance;
    }
 public String getApplicationUrl(){
//your logic
  }
}

In TestClass use the SingletonExample class.

public class Homepage {

    @Test
    public void NavigateToHomePage() throws MalformedURLException {
       
      
    	driver.get(SingletonExample.getInstance().getApplicationUrl());
      
       
    }
}

B)Builder Pattern: The builder pattern is another useful design pattern that can be used in test automation. This pattern allows the creation of multiple representations of an object. Its aim is to separate the construction of a complex object from its representation so that the same construction process can create different representations.

For example, we have a user object that has 5 attributes. Among these 2 fields are mandatory and the rest are optional and the requirement is to create a class immutable. So How we will manage, by creating more constructors? The answer is “NO” otherwise we will lose the readability and the immutability.

We can solve this problem by using a builder pattern. Builder design pattern helps in minimizing the number of parameters in the constructor and thus there is no need to pass in null for optional parameters to the constructor.

Let’s create a User class:

package com.test.demo;

public class User {
	
	private String name;
	private String address;
	private String id;
	private boolean isactive;
	
	public User(String name,String address,String id, boolean isactive)
	{
		this.name=name;
		this.address=address;
		this.id=id;
		this.isactive=isactive;
	}

	
	public String getName() {
		return name;
	}

	public String getAddress() {
		return address;
	}

	public String getId() {
		return id;
	}

	public boolean isIsactive() {
		return isactive;
	}

}

Create a Builder class to implement the build pattern:

package com.test.demo;

public class UserBuilder {
	
	private String name;
	private String address;
	private String id;
	private boolean isactive;
	UserBuilder()
	{
		
	}
	
	public static UserBuilder builder()
	{
		return new UserBuilder();
		
	}
	
	public UserBuilder setName(String name)
	{
		this.name=name;
		return this;
	}
	
	public UserBuilder setaddress(String address)
	{
		this.address=address;
		return this;
	}
	public UserBuilder setid(String id)
	{
		this.id=id;
		return this;
	}
	public UserBuilder isactive(boolean isactive)
	{
		this.isactive=isactive;
		return this;
	}
	
	public User build()
	{
		return new User(name,address,id,isactive);
	}

}

Create a test class to test this builder pattern:

package com.test.demo;

import static org.testng.Assert.assertEquals;

import org.testng.annotations.Test;

import io.restassured.http.ContentType;
import io.restassured.response.Response;

public class BuilderPattern {
	
	@Test
	public void postJsonUsingBuilderPattern()
	{
		User user=new UserBuilder().setName("varsha")
				.setaddress("abc building")
				.setid("1245").build();
		
		Response response = given().baseUri("http://localhost:4001").contentType(ContentType.JSON).log().all()
				.body(user).post("/users");
		assert.assertEquals(response.getStatusCode(),"201");
		
	}

}

By using the builder pattern we have successfully passed the mandatory and optional fields without losing their readability.

2)Structural Design Patterns: These patterns are designed with regard to a class’s structure and composition. The main goal of most of these patterns is to increase the functionality of the class(es) involved, without changing much of its design. ex: Page Object Models, Facade patterns.

A)Page object Pattern: The Page Object Model is a widely used object design pattern for structuring automation test code. Here, pages in the app are represented as Classes, and various UI elements of that pages are defined as variables.

B)Facade Pattern: Facade pattern provides a simple interface to deal with complex code. This pattern is basically just the extension of POM. A facade is mostly used to combine a few page objects/actions and provide uniform actions for consumers. For example, when a complex API needs to be executed in a specific order, create a facade for the designated functionality and provide a simplified interface for operating.

3)Behavioural Design Patterns: These patterns are designed depending on how one class communicates with others. How are some independent of others? ex: Observer pattern, iterator pattern, strategy pattern, etc.

Design patterns do not guarantee an absolute solution to the problem, however, they provide the clarity and possibility to build a better framework. Add a pattern when there’s a practical need for one. Sometimes the simplest solution is no pattern at all.