Design patterns QA engineer should know

In a test automation we may think that our code contains all the benefits provided by the Object Oriented language. The code should be re-usable so that we can re-use it anywhere. The code we write should be flexible so that we can make any changes to it with less or any pain. Any changes to a part of the code should not affect any other part of the code.

In order to design the code in such a way which allow our code to be flexible, robust, maintainable, and re-usable we should use design patterns. Design Patterns are predefined and provides industry standard approach to solve a recurring problems.

The design patterns contribute to a major chunk in defining the test automation best practices. The benefits of test automation cannot be gained effectively without implementing the required design patterns specific to a test automation project.

I am going to give an overview with examples of some design patterns that can be very useful in your test automation projects.

1) Singleton Pattern
Sometimes there is a situation some classes to have exactly one instance.
You may require only one object of a class, for example, when you are a creating a webdriver instance.

More than one object of that type clearly will cause inconsistency to your program.

The Singleton Pattern ensures that a class has only one instance, and provides a global point of access to it.

How to create a class using the Singleton Pattern?
In general, we follow the below steps to create a singleton class:
1) Override the private constructor to avoid any new object creation with new operator.
2) Declare a private static instance of the same class.
3) Provide a public static method that will return the singleton class instance variable. If the variable is not initialized, then initialize it or else simply return the instance variable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class SingletonDemo {

 private static SingletonDemo instance = null;

 private SingletonDemo() {
 }

 public static SingletonDemo getInstance() {
  if (instance == null)
   instance = new SingletonDemo();
  return instance;
 }
}

In the above code, getInstance() method is not thread-safe. Multiple threads can access it at the same time and for the first few threads when the instance variable is not initialized, multiple threads can enters the if loop and create multiple instances and break our singleton implementation.

Implementation of Singleton in Java, in a multithreaded environment.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class SingletonDemo {

 private static SingletonDemo instance = null;

 private SingletonDemo() {
 }

 public static SingletonDemo getInstance() {
  if (instance == null) {
   synchronized (SingletonDemo.class) {
    if (instance == null)
     instance = new SingletonDemo();
   }
  }
  return instance;
 }
}

2) Factory Method Pattern
  • Creates an instance of several derived classes.
  • Create an object without exposing the creation logic to the client.
  • An Interface specifies all standard and generic behavior and then delegates the creation details to concrete classes that are supplied by the client via factory class.
Factory Method pattern provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

As long as all product classes implement a common interface, you can pass their objects to the client code without breaking it.

For example, In a lab Xerox, HP classes implements a printer interface, which declares methods called getName() and getIP(). Each class implements these methods differently: Every printer has different names and IP. The factory method in the Xerox class returns xerox printers, whereas the factory method in the HP class returns HP printers.

Below is a code of PrinterFactory

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class PrinterFactory {

 // use getPrinter method to get object of type printer
 public Printer getPrinter(String printerType) {
  if (printerType == null) {
   return null;
  }
  if (printerType.equalsIgnoreCase("HP")) {
   return new HP();

  } else if (printerType.equalsIgnoreCase("Xerox")) {
   return new Xerox();

  } else if (printerType.equalsIgnoreCase("Cannon")) {
   return new Cannon();
  }

  return null;
 }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class FactoryDemo {

   public static void main(String[] args) {
      PrinterFactory printerFactory = new PrinterFactory();

      //get an object of Circle and call its draw method.
      Printer p1 = printerFactory.getPrinter("Xerox");

      //call draw method of Circle
      p1.getIP();
   }
}

3) Builder Pattern
The builder pattern is construct a complex object from simple objects using step-by-step approach. 

The process of constructing an object should be generic so that it can be used to create different representations of the same object.


What problem Builder pattern solves in Java?
In a class Employee some of the instance attributes are required while the rest are optional. What kind of constructors should you write for such a class? A first option would be to have a constructor that only takes the required attributes as parameters, one that takes all the required attributes plus the first optional one, another one that takes two optional attributes, and so on.

The builder pattern takes the advantages of the safety of telescoping constructor pattern and readability of the code.

What Is the Builder Pattern?
First, Let’s see how we can implement a builder design pattern.

1) First of all, you need to create a public static nested class, which has all the instance attributes from the outer class. The naming convention for Builder usually is that and if the class name is Employee, then the builder class should be named as EmployeeBuilder.
2) The outer class Employee should have a private constructor that takes a EmployeeBuilder object as its argument.
3) The builder class should have a public constructor with all the required attributes as parameters and these required attributes are defined as "final," which have setter methods to set the optional parameters. It should return the same Builder object after setting the optional attribute.
4) The final step is to provide a build() method in the builder class that will return the outer class object to the client. This build() method will call the private constructor in the outer class, passing the Builder object itself as the parameter to this private constructor.

5) The Employee class has only getter methods and no public constructor. So, the only way to get an Employee object is through the nested EmpolyeeBuilder class.

The class using Builder pattern will look like this:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package easy.string;

public class Employee {
 private final String firstName; // required
 private final String lastName; // required
 private final int age; // required
 private final int personalId; // required
 private final String phone; // optional
 private final String address; // optional
 private final String mail; // optional

 public static class EmployeeBuilder {
  private final String firstName; // required
  private final String lastName; // required
  private final int age; // required
  private final int personalId; // required
  private String phone; // optional
  private String address; // optional
  private String mail; // optional

  public EmployeeBuilder(String firstName, String lastName, int age, int personalId) {
   this.firstName = firstName;
   this.lastName = lastName;
   this.age = age;
   this.personalId = personalId;
  }

  public EmployeeBuilder setAddress(String address) {
   this.address = address;
   return this;
  }

  public EmployeeBuilder setPhone(String phone) {
   this.phone = phone;
   return this;
  }

  public EmployeeBuilder setMail(String mail) {
   this.mail = mail;
   return this;
  }

  public Employee build() {
   // call the private constructor in the outer class
   return new Employee(this);
  }
 }

 private Employee(EmployeeBuilder builder) {
  this.firstName = builder.firstName;
  this.lastName = builder.lastName;
  this.age = builder.age;
  this.personalId = builder.personalId;
  this.phone = builder.phone;
  this.address = builder.address;
  this.mail = builder.mail;
 }

 public String getFirstName() {
  return firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public int getAge() {
  return age;
 }

 public int getPersonalId() {
  return personalId;
 }

 public String getPhone() {
  return phone;
 }

 public String getAddress() {
  return address;
 }

 public String getMail() {
  return mail;
 }
}


We can create an Employee object from client code as follows:
1
2
3
4
5
6
7
8
public class EmployeeTest {
    public static void main(String[] args) {
        Employee employee = new Employee.EmployeeBuilder("Cristiano", "Ronaldo", 33, 7)
                .setPhone("0045-1234556")
                .setAddress("Juventus")
                .setMail("CR@Juventus.org").build();
    }
}


4) Prototype Pattern
The Prototype pattern is generally used when we have an instance of the class (prototype) and we'd like to create new objects by just copying the prototype.

In some games, we want trees or buildings in the background. We may realize that we don't have to create new trees or buildings and render them on the screen every time the character moves.

So, we create an instance of the tree first. Then we can create as many trees as we want from this instance (prototype) and update their positions. We may also choose to change the color of the trees for a new level in the game.

The Prototype pattern is quite similar. Instead of creating new objects, we just have to clone the prototypical instance.


How to implement prototype pattern?
One of the ways we can implement this pattern in Java is by using the clone() method. To do this, we'd implement the Cloneable interface.

When we're trying to clone, we should decide between making a shallow or a deep copy. Eventually, it depends on the requirements.

For example, if the class contains only primitive and immutable fields, we may use a shallow copy.

If it contains references to mutable fields, we should go for a deep copy. 

Let's take the example we mentioned earlier and proceed to see how to apply the Prototype pattern without using the Cloneable interface. In order to do this, let's create an abstract class called Tree with an abstract method ‘copy'.

1
2
3
public abstract class Tree {
    public abstract Tree copy();  
}  

1
2
3
4
5
6
7
8
public class PlasticTree extends Tree {
    @Override
    public Tree copy() {
        PlasticTree plasticTreeClone = new PlasticTree(this.getMass(), this.getHeight());
        plasticTreeClone.setPosition(this.getPosition());
        return plasticTreeClone;
    }
}


1
2
3
4
5
6
7
8
public class PineTree extends Tree {
    @Override
    public Tree copy() {
        PineTree pineTreeClone = new PineTree(this.getMass(), this.getHeight());
        pineTreeClone.setPosition(this.getPosition());
        return pineTreeClone;
    }
}

So here we see that the classes which extend Tree and implement the copy method can act as prototypes for creating a copy of themselves.
Prototype pattern also lets us create copies of objects without depending on the concrete classes. Let's say we have a list of trees and we would like to create copies of them. Due to polymorphism, we can easily create multiple copies without knowing the types of trees.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class TreePrototypesUnitTest {
 
    @Test
    public void givenAPlasticTreePrototypeWhenClonedThenCreateA_Clone() {
 
        PlasticTree plasticTree = new PlasticTree(mass, height);
        plasticTree.setPosition(position);
        PlasticTree anotherPlasticTree = (PlasticTree) plasticTree.copy();
        anotherPlasticTree.setPosition(otherPosition);
 
        assertEquals(position, plasticTree.getPosition());
        assertEquals(otherPosition, anotherPlasticTree.getPosition());
    }
}

We see that the tree has been cloned from the prototype and we have two different instances of PlasticTree. We've just updated the position in the clone and retained the other values.

2 comments:

  1. Thanks for sharing this informative article on Design patterns QA engineer should know. If you want to Hire QA Engineers for your project. Please visit us.

    ReplyDelete
  2. Thanks Vinod, nice article.

    ReplyDelete