Rock SOLID Test Automation

Do you already have a written test automation project? Awesome! Now, are you satisfied with how it is written? How do you even know if it’s written correctly? In test automation projects, like any other software project, we follow a set of principles and assumptions, work methods, design patterns and best practice we’ve learned from past experience. In this post, I will discuss a group of principals for writing code called: SOLID, and how we can utilize them in our test automation projects.

First of all, let’s understand what is SOLID?
SOLID is a work method that allows us to build a loosely-coupled software project. So before we explain SOLID’s acronym and what it is, let’s first understand what “loosely-coupled” means.

According to Wikipedia, loosely-coupled is a method in which you separate and isolate components, so that each component will have one defined role. Contrary to that, with the tight-coupling method, classes are necessarily dependent to one another. Still not so clear? I think the best way to explain what loosely-coupled and tight-coupling is, would be with an example outside of our software world  😉

Let’s have a look at smartphone devices. Let’s say, for example, we bought an iPhone 7 two years ago, and today its battery is very weak. We couldn’t simply change the battery, but would rather need to replace the entire device. The iPhone battery comes built-in the device and there’s no way to take it out. This is what we call tight-coupling (dependency between the device’s components).

Now, let’s take a Xiaomi mobile device for example. You can easily take out its battery, thus if it’s very weak – all you’ll need to do is buy a different Xiaomi battery (and not an entirely new device). This is what we call loosely-coupled (separation between the device’s components).

So, as mentioned above – SOLID principles utilize the loosely-coupled method (the same as the Xiaomi mobile device, in which you can separate the battery). SOLID’s acronym is:

  • Single Responsibility Principle (SRP)
  • Open Closed Principle (OCP)
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principles (ISP)
  • Dependency Inversion Principle (DIP)

The Single Responsibility Principle refers to the responsibilities of the classes/functions/modules, each one has only one responsibility.

The Open Closed Principle refers to the fact that the classes/functions/modules are open to extensions but closed to modifications.

The Liskov Substitution Principle (named after Barbara Liskov who won the Turing Award in 2008) refers to the fact that we can substitute objects of different classes that have an inheritance connection. You can replace an object from a parent class with an object from a child class, without changing the parent’s behavior.

The Interface Segregation Principle refers to a work method in which we will want to separate between different interfaces in order to avoid implementing unnecessary processes. In other words, if needed, we can create as many interfaces as we’d like to.

The Dependency Inversion Principle refers to the separation between the components for better code maintenance, and it occurs by abstraction while using interfaces instead of classes.

For further info about the SOLID principles, you can check out Tomer Cohen’s blog (in Hebrew), who explains with examples all about the principles. I also recommend Tim Corey’s blog who has example videos for each and every one of the SOLID principles.


So, how can we incorporate the SOLID principles in our test automation project? 
Let’s have a look at an example.

We have a project supporting Web (Selenium) and Mobile (Appium) apps. What will be a simple thing for us to do? If we’re running Web tests, then we’ll initiate Selenium’s driver. If we’re running Mobile tests, then we’ll initiate Appium’s driver. While initiating Selenium, we basically need to check again on which browser we want to work (meaning, we’ll have here if/else if or switch/case strings). This is how our code is supposed to look like:

if (getData("AutomationType").equalsIgnoreCase("Web"))
  initBrowser(getData("BrowserType"));
else if (getData("AutomationType").equalsIgnoreCase("mobile"))
  initMobile();

The getData function takes the data (the definition of what we want to work on) from an external file that can be in one of these formats: ini/txt/json/xml/properties etc…
And in case the AutomationType=web, then we will turn to the initBrowser function with a BrowserType parameter that incorporates the browser’s name we want to work with. It will look as follows:

switch (browserType.toLowerCase())
{
case "firefox":
  driver = initFFDriver();
  break;
case "ie":
  driver = initIEDriver();
  break;
case "chrome":
  driver = initChromeDriver();
  break;
}

The initFFDriver method will initiate the Firefox driver, the initIEDriver will initiate the IE driver, etc.

In this code example, everything will work for us, but we did not implement here any SOLID principles. In order to refactor this code according to what we’ve learned in this post so far, we will have to change a few things, starting with creating interfaces and implementing classes.

Let’s define 2 interfaces: client and browser. The classes that will implement the client will be Web and Mobile, which means that in the future we will be able to create additional classes, such as: API, desktop, etc.

The Web class will be an abstract class that will implement the client and will define an abstract function in which we will initiate the browser (initBrowser). This is the browser interface:

package Interfaces;

import org.openqa.selenium.WebDriver;
public interface browser
{
  public WebDriver initBrowser();
}

And this is the client interface:

package Interfaces;

public interface client
{
  public void initClient();	
}

Let’s define our Mobile class that implements the client:

package Classes;

import Interfaces.client;

public class mobile implements client
{
  public void initClient()
  {
    // TODO Implement this
  }
}

And the Web class:

package Classes;

import org.openqa.selenium.WebDriver;
import Interfaces.*;

public abstract class web implements client, browser
{
  public void initClient()
  {
    initBrowser();
  }
  
  abstract public WebDriver initBrowser();
}

This means that in the future when we want to support additional clients, we won’t need to change the existing functionality, but rather simply add another class designed to initiate the additional client through its driver, exactly according to the SOLID principles. For instance, if we’d want to support automation for desktop apps, we’ll create a new class as follows:

package Classes;

import Interfaces.client;

public class desktop implements client
{
  public void initDesktop()
  {
    // TODO Implement this
  }
}

The Chrome class will inherit from Web:

package Classes;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

import Infra.getData;

public class chrome extends web
{
  public WebDriver initBrowser()
  {
    System.setProperty("webdriver.chrome.driver", getData.go("ChromeDriverPath"));
    return new ChromeDriver();
  }
}

And the Firefox class will inherit from the Web as well:

package Classes;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;

import Infra.getData;

public class firefox extends web
{
  public WebDriver initBrowser()
  {
    System.setProperty("webdriver.chrome.driver", getData.go("FFDriverPath"));
    return new FirefoxDriver();
  }
}

This means that in the future when we want to support additional browsers, we won’t need to change the existing functionality, but rather simply add another class designed to initiate the additional browser through its driver, exactly according to the SOLID principles. For instance, adding support for IE browser:

package Classes;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.internetexplorer.InternetExplorerDriver;

import Infra.getData;

public class ie extends web
{
  public WebDriver initBrowser()
  {
    System.setProperty("webdriver.ie.driver", getData.go("IEDriverPath"));
    return new InternetExplorerDriver();
  }
}

 

We’ve seen here a “real” automation project which you have probably already created, but in this case, we’ve replaced the switch/case and are working with SOLID principles, a very popular work method in the software field. Of course, we can elaborate and improve the existing code even further – Using page object model, test classes, etc.

Does your work method include SOLID principles? Please share your experience in the comments below!

Reference: http://atidcollege.co.il