Design Patterns in Automation Testing.

When we design test automation framework it is a common practice to follow the Test Design Patterns for well architecture & matured framework. A poorly designed architecture is a major reason why test automation frameworks fail. Below are the few common factors which results into the bad design :

  • Those implementing the work are new or unfamiliar with test automation approaches/standards.
  • The project’s timeline is strict.
  • The scale is unclear.
  • Not using the right coding principles.

But , there is a fix .Lets take a moment to identify our issues & problems .Once the issues are identified , it becomes easy to build maintainable , flexible and extendible frameworks.

Design patterns: A better software testing technique

Design patterns are used extensively in programming and they generally offer a reusable solution to a known occurring problem. It is like having a set of best practices into our code and usually result in more flexible and maintainable code.

Design patterns are grouped into three categories:

  • Structural patterns explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient.
  • Creational patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code.
  • Behavioural patterns are concerned with algorithms and the assignment of responsibilities between objects.

In this article, we will see some of the most popular structural, creational, and behavioural design patterns.

Before diving into design patterns, it’s important to become familiar with SOLID principles that helps to make software design more understandable, flexible, and maintainable.

  • The Single Responsibility Principle — A class should have one, and only one reason to change.
  • The Open Closed Principle — You should be able to extend a class behavior without modifying it.
  • The Liskov Substitution Principle — Derived classes must be substitutable for its base classes.
  • The Interface Segregation Principle — Make fine grained interfaces that are client-specific.
  • The Dependency Inversion Principle — Depend on abstractions, not on concretions.

Structural design patterns

Page Object Models (POM)

This is the most popular structural design pattern and is commonly used in building test automation frameworks to automate UI test cases. The pattern abstracts any page information away from the actual tests

POM’s are beneficial because:

  • We don’t need to write duplicate code. We create page components (e.g., input fields, buttons, etc.) which are used when building other page objects.
  • The pattern provides encapsulation and abstraction. Page objects encapsulate web elements, or other class members and provide access to wrapper methods that hide all the logic (i.e., the mechanics used to locate or change data).
  • Creating and organising page objects makes it easier to understand the structure of the automation solution via usage, new test creations, creation of new page components, and readability of tests.

Page objects are like interfaces for web pages or web page components, also known as loadable components. These components support the same interaction with other web pages and should consist of:

  • preferably encapsulated web elements
  • other page objects
  • methods to operate with page object and return other page objects by design
  • no assertions should be used on page objects

To implement POM effectively, follow a folder and file structure to organise and keep page objects easily accessible. Page objects can be implemented in a functional or structural manner, depending on the approach and need.

Composite patterns

This pattern composes objects into tree structures to represent part-whole hierarchies. Composites let clients treat individual objects and compositions of objects uniformly.

A composite pattern is beneficial because:

  • The simplified representation of a complex tree structures components as branches with the help of polymorphism and recursion.
  • Implementing new items to the structure without altering the code (Open/Close principle) is easy.

We use a composite pattern to create components that benefit from a tree structure (e.g., menus needing sub-menus, sub-sub-menus, etc.) to get, select, or print all the items via recursion. For more complex needs, include custom iterators.

Facade patterns

The Facade pattern provides a simple interface to deal with complex code.In the facade pattern, as applied to test automation, we design the facade class which has methods that combine actions executed on different pages.

A façade pattern is beneficial because:

  • Developers are able to create structure, consisting of layers of subsystems.
  • Developers are able to simplify/hide code complexity from consumers.

In test automation, 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.

Creational design patterns

Factory method patterns

In the factory design pattern we have a super class with multiple subclasses and based on some input, we need to return a particular subclass. It is often used when a class cannot anticipate the type of objects it needs to create beforehand. Here, instantiation of a class is done from the factory class. So when we need to create the object based on particular input this pattern is used.

One of the best examples of a test automation framework is with the Selenium WebDriver initialization. While most of the initializations require some kind of setup afterward, when all Webdrivers share the same “IWebDriver” interface, refer Webdrivers through that interface. Adding another Webdriver (if necessary) is easy. Simply extend the factory method, which does not modify already existing code.

Builder patterns

This creational design pattern lets developers construct complex objects step by step. The pattern produces different types and representations of an object using the same construction code.

A builder pattern is beneficial because:

  • Developers are able to create multiple representations of an object.
  • The pattern isolates complex and the repetitive construction code in an object.

As with any automation solution for software, a builder pattern is used to create an object needing many different representations. Representations with telescopic constructors are hard to maintain with difficulties generating readable code. Use the builder pattern to create fluent page objects. A builder pattern is easy to implement and understand.

When we need to build already predefined objects, consider introducing a director-class responsible for wrapping the builder defined steps in some kind of order (e.g., buildCRUD(Builder builder) which calls addCreate(), addRead(), addUpdate(), and addDelete() builder steps).

Singleton patterns

This pattern ensures a class has only one instance and provides a global point of access.

A singleton pattern is beneficial because:

  • There’s only one instance of an object. In most cases, the pattern is used for Logger, Connections, or External Resources.
  • Developers need a global access point to a class instance.

Behavioral design patterns

Strategy patterns

This pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy patterns let the algorithm vary independently from the clients that use it.

In test automation, strategy patterns are useful in various fields. For example, an application includes multiple payment methods on a checkout page or a different implementation on the same action, like account creation. One pattern uses UI, and another uses RESTful calls.

These algorithms (strategies) are easily defined, encapsulated, and used for various needs. Engineers take all the logic from page objects and lets page objects delegate all the work to the strategy objects.

Summary

Design patterns and its application is an exceptionally worthwhile investment, despite the initial learning curve.

To ease and speed up development test automation:

  • Constantly evaluate and identify potential issues.
  • Review the current design before implementing a new feature
  • Keep engineers up to date on current objectives and future plans.
  • Document most of the agreements on paper
  • Have a backlog and actively work on corresponding tasks.

https://www.devbridge.com

https://en.wikipedia.org/wiki/SOLID

https://kobiton.com/

https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle

https://dzone.com/articles/design-patterns-in-automation-testing

Thank you for reading this article. Any feedback for improvements would be much appreciated.!