Design Patterns for QA Automation: Build effective test solutions

Kostiantyn Teltov
10 min readJul 25, 2022

--

phew! Good. Finally I have finished this article.

Hi to all colleagues who are involved to QA Automation. Once you’ll decide you are tired to copy paste tests code from one test to another and you’re ready to build Automation Solution/’s (I don’t like a word “Framework”). You probably have seen a few of the projects and think - “I know how to do it”. It’s good you have some experience. Anyway, it will be good to familiarize with some Design Patterns helps you to build test solution more effectively.

In this article, I’ll try to highlight the Design Patterns I commonly use during test solutions creation. Some of them are used frequently, some very rarely. It’s up to you what you’re gonna use. But you will know how it can be used.

Ready? Let’s go!!!

Page Object

All QA’s who automate scenarios in browser know about this design pattern. This one is must have. The goal of using page objects is to abstract any page information away from the actual tests.

When you work with page elements you need to store element locators. The problem is HTML markup can be changed and it can break your locators. Also, you may need to use this locators not only for the one tests. So, it reasonable to store these locators and methods in separate classes.

Usually, you create separate class for each of the page.

Inside of this class you may have selectors, locators and methods can be reused in the different tests

It may be a case, when your page contains a lot of the sections. In such case you may also split your page on sections and create page fragments. Basically you may left reference of to this fragment inside of the page class. E.g. in example below I have a page called “TablePage” (I know, sounds stupid) and three fragments inside.

I don’t want to stay longer on this pattern. You can find a lot of examples in the internet. Anyway, I think you already familiar with this pattern if you perform automation of e2e scenarios.

Factory Method

Creates objects without specifying the exact class to create

So, let’s imagine you want to run your tests in a few browsers and you don’t know, maybe in the future you will have to do it in some others too (The same with Databases).

So, you need to create an object of specific Browser e.g. ChromeDriver. Adding new Driver into the app would require making changes to the entire codebase. And then you will decide to test in other browsers.

As a result, you will end up with pretty nasty code, riddled with conditionals that switch the app’s behavior depending on the class of transportation objects

So, what you need to do is just to create some static class where you’ll initialize object depends on configuration you need and return it. E.g. you may use switch statement.

Then, you can just call created static method.

The cool thing, you can always extend you static method with a new version of WebDriver and it will not be affected on top level.

This is very often used design pattern.

Abstract Factory

Allows the creation of objects without specifying their concrete type

Let’s imagine a case, when you need to access your DB inside of the tests. You know, application configuration may work with some SQL and Non-SQL database. It might be you expect it in the future.

The problem is we don’t want to create second version of test for each database. We just need to access it and manipulate with abstract DB. Abstract Factory helps you yo resolve this problem.

Let’s imagine we need to access two tables or documents depending on DB Type: Orders, Users

Create two Interfaces: IOrders, IUsers. Inside of this interfaces you may define some method signatures you want to implement. E.g. you may want to get some record by id.

Create Abstract Factory Interface and add IOrders, IUsers inside.

Now, you you need to create classes implement IOrders, IUsers interface for each of the provider. So, you will have something like OrdersSql, OrdersCosmos implement IOrders, UsersSql, UsersCosmos imeplemnt IUsers.

It’s left only to create Concreate Factories classes to each of the provider. So, create SqlDbManager and CosmosDbManager classes, both implement IDataBaseManager. Inside of the each classes you’ll need to initialize objects of your classes

E.g. for SqlDbManager:

public IOrders Orders() => new OrdersSql();

public IUsers Users() => new UsersSql()

The should be done for CosmosDb class but you need to initialize CosmosDb objects

So, depends on your application configuration you need to initialize some specific DB type e.g.

protected IDataBaseManager Database= new SqlDbManager();

or

protected IDataBaseManager Database = new CosmosDbManager()

Note: You may use Abstract method design pattern we have already considered above.

Inside of your test you may access database like this Database.Orders().GetRecordById(1).

As you may see you just use Interface signature and don’t know what database used inside.

Final diagram looks like this:

I believe this is very cool pattern and can be used very often.

Builder

Let’s imagine, you want to build Rest API services test solution. You have a new endpoint: <server>/<api_version>/users.

This endpoint support POST, PUT, GET(by id or query params), DELETE methods

JSON response model:

So, let’s create a builder class. Initialize new object of model and Specify search endpoint inside of the constructor.

Let’s build the methods will fulfil model (will be converted to JSON body during request)

We can do the same with Query Param builder

Finally, create the methods send request in the end

You builder is ready to be used. Now you may play it as with constructor

PUT request builder example:

GET with query request builder test example:

There are a lot of other options how you can use builder pattern. As an example you you have a very big model class and you want to specify only part of the properties. I just showed you the one of my real project examples.

Prototype

Creates a new object from an existing object

Let’s imagine you have a model with a lot of properties and you need to create an objects list with almost the same property values. This design pattern will help you to avoid of writing a lot of lines of code.

Create interface with method signature returns this interface

Now you can add this interface to the class you are planning to clone

Now, you may use it in code and clone you object and change only some small part of the new object

Singleton

Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance. Sometimes it’s also called as anti-pattern. Anyway it can be useful in some cases.

The most common reason for this is to control access to some shared resource — for example, a database or a file.

Let’s imagine we want to have only once instance of WebDriver.

What we need to do is to create a class with WebDriver initialization inside of the private constructor.

private properties of WebDriver and current class instance should be created too.

Next step is to create public static Instance property. On Get of this property, we should verify if current class instance exist and initialize instance of the class in case if we it’s null.

Finally, we can create a method returns WebDriver.

Now, we can call our Instance as many times as we want, but it will return the same instance of the WebDriver. So, if we already initialized it in some other test, it still will be available for others.

Facade

Provides a simple interface to a more complex underlying object

So, let’s imagine you have build a lot of request builders. You can create instance of the class each time when you build your constructor. But you may also want to have only one entry point. In this case, you may use Facade design pattern

The solution is very simple, just create a class (Facade) and place instantiation of builder classes.

Now, instead of using builder class instantiation, you can just access it from Facade class.

You may want to combine it with Singleton or Lazy initialization (read further) if you need it.

Object pool

Object pool pattern is a software creational design pattern which is used in situations where the cost of initializing a class instance is very high

Imagine a case in your test solution you want to work with a lot of WebDriver instances. If you run your tests in parallel it may eat a lot of memory if you will try to create and destroy WebDriver instances. So, one of the options is to create a pool of instances, then GET available from this pool and put it back when you don’t need to use it.

So, you have some object,

You need to create a class will Get and Release objects from the list. If you look inside, you’ll see we have two collections of objects: Available and InUse. So, with help of the static methods we just manipulate with these collections.

Now, we can work with our object pool

Note: Please be attentive, probably it will be better to use Thread-safe collections in case if you work with tests in parallel. I just showed you sync code example.

Alternative case example: On one of my projects, we had an API can work with only one Client when some data in InProgress status. So, in order to run tests in parallel it was required to have more Clients data. I decided to retrieve Clients data and put them to thread-safe Stack. My tests took clients data, worked with this data and put it back to the Stack. So, tests was running in parallel with available Clients.

Dependency Injection

Dependency Injection (DI) is a design pattern allows the creation of dependent objects outside of a class and provides those objects to a class through different ways. Using DI, we move the creation and binding of the dependent objects outside of the class that depends on them.

So, let’s imagine we need to provide some Database instance to some method or class. The problem is, today we use SqlInstance, but maybe tomorrow we will decide also to work with some non-SQL e.g. Mongo.

At this moment, our option is to only change the Program class and start using

The idea is we can just provide some interface instead of creation instance inside of this method/class.

So, we already have example of IDatabase interface. There are 3 options to provide it:

Constructor injection

Create private field of interface type and instantiate it from constructor. Now you can use it inside of class.

Method injection

Create a method with a parameter interface type. Will be available only in scope of the method. But maybe that’s exactly what you need.

Property injection

As you may understand you just need to create a property of interface type. It can be used to all class members.

What approach to use it always depends on case. So, you need to choice ones preferable to you.

Lazy Initialization

Lazy initialization is a technique that defers the creation of an object until the first time it is needed. In other words, initialization of the object happens only on demand.

So, that’s it:) If you don’t want to initialize object or objects list each time you instantiate a class, you may find this pattern/technique is very useful. Actually, we have already seen this construction before in the Singleton Design pattern section

Only one thing, there is more modern way to do it in C#

Conclusion

There are a lot of other Design Patterns exist. I would recommend you to read about each of them (Start from “Gang of four design patterns”). Anyway, I don’t think it’s necessary to use all of them in Automation Tests Design. In this article I showed all design patterns I have used at least once. It maybe also interesting to hear if you have some successful experience with other Design Patterns.

Thanks for those who has finished this long article. Hope you will find some of these patterns are useful.

--

--

Kostiantyn Teltov

From Ukraine with love. QA Tech Lead/SDET/QA Architect (C#, JS/TS, Java). Like to build testing processes and help people learn. Dream about making indie games