One way testers reduce maintenance costs is by writing better test scripts, but what about reducing the test automation technical debt? I presume we are familiar with terms like library, framework, test automation framework, and test automation tool. Still, test automation engineers often misunderstood those terms. This is essential knowledge and an excellent opportunity for continuing education.
Let’s look at SOLID: a mnemonic acronym for five design principles. The goal of those principles is to help us write more understandable, flexible, and maintainable software. SOLID stands for:
SRP – Single Responsibility Principle
The principle states, “Every software module should have only one reason to change,” which means that every class, method, among others, can do many things, but they should serve a single purpose.
OCP – Open/Closed Principle
The principle states that “software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.” If new requirements are written for the already implemented functionality, these can be added without changing the whole structure of the existing code that has been already unit tested.
LSP – Liskov Substitution Principle
The principle states that “you should be able to use any derived class instead of a parent class and have it behaved similarly without modification.” In single words, the child class should not modify or change how the base class behaves. The central point is that the child classes can be used as if they are their parent themselves, but unexpected issues will occur if you change behavior in a child class.
ISP – Interface Segregation Principle
The principle mentions that “clients should not be forced to implement interfaces they don’t use. Instead of one large interface, many little interfaces are preferred based on groups of methods, each one serving one sub-module.” Again, each interface should provide methods that serve a single purpose (SRP principles).
DIP – Dependency Inversion Principle
The principle states that “high-level classes should not depend on low-level classes.” It is because the high-level classes usually contain business logic. On the other hand, the low-level ones consist of actions such as CRUD DB operations, reading files, or calling web APIs.
Understanding Test Design Patterns
Let’s start by defining the term; a design pattern is a prescribed solution to everyday software challenges. It doesn’t consist of code or any specified algorithm, but instead, it describes how to group your logic smartly, reuse it, or make it easier to maintain. Thus, it is a template for solving design problems, which we can use while creating our software solutions.
For example, imagine some design problems in your test automation. How much time do you need to figure out where the problem is? Is it an automation bug or an issue in the system under test? The better the maintainability, the easier it is for us to support our existing code, accommodate new requirements, or fix bugs in our test scripts.
If you are working on a test automation project or implementing a test automation framework from scratch, your first option is the Page Object pattern (POM). Still, the POM is not always the most suitable option for your current project.
The main idea behind this article is to know better about other patterns and how we can adapt to our current test automation projects. We need to determine which model suits our everyday necessities and solves our design problems.
Observer Design Pattern
The Observer pattern is often considered as part of the Model-View-Controller framework. It can reduce the number of tests required when implementing test automation. The Observer design pattern establishes a one-to-many dependency between objects so that when one object changes its state, all dependents will notify and update automatically.
In addition, this pattern will provide an easy way for test automation engineers to add extra logic to the current test execution via test level characteristics. For example, start and reuse the browser if the previous test is working and no failures.
In the Observer design pattern, the subject is the object that includes the state and controls it. So, there is one subject with a state. On the other hand, the observers use the state condition, even though they don’t own it. Many observers rely on the subject to inform them when its state transitions. So, there is a relationship connecting the one subject and the many observers.
If the Observer pattern is used, you can decide to have one automated test to verify that one of the views gets updated. It will confirm the notification that the Observer pattern is working and ensure the view is updated correctly. If every view is listening to the model, all other views will automatically get updated if one view gets updated. To ensure each view is listening to the model, you can add a test to confirm all views are listed in the Observer Class.
The Screenplay Pattern is based on software engineering principles like the Single Responsibility Principle and the Open-Closed Principle. However, it prefers composition over inheritance and employs logic from Domain Driven Design to reflect the domain of performing tests, steering you towards the effective use of layers of abstraction.
In the Screenplay pattern, tests describe how a user interacts with the application to accomplish a goal. A user interacting with the system is called an “Actor.” Actors are the central part of the Screenplay pattern. Although, as a Screenplay, the actors have one or more “Abilities,” such as the ability to browse the web or query a web service, the actors can also perform “Tasks,” such as adding an item or removing items.
To interact with the application, such as entering values into the fields or clicking on buttons, the actors need to interact with the applications; these interactions are “Actions.” Actors ask “Questions” about the state of the system, e.g., reading the value of a field on the screen or querying a web service; this is the way to test using the Screenplay pattern.
Factory Design Pattern
The factory design pattern states that we need to have a superclass with many subclasses, and based on some input, we need to return a particular subclass. A factory method pattern is a creational pattern used for encapsulating the instantiation of concrete types, avoiding tight coupling between the creator and concrete products. As a result, engineers do not need to modify any code to extend the codebase.
The Factory design pattern could be beneficial when designing the test scripts. It will hide the logic of initializing an object inside the factory (Browser initialization) and refer to the object using a standard interface instead of the concrete class (Avoid unnecessary driver instances).
We mentioned just a few software patterns, but there are more. Therefore, my recommendation for a continuous learner is to read the book “Design Patterns: Elements of Reusable Object-Oriented Software, written by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. The book describes 23 classic software design patterns to use in implementing test automation.
Lastly, there is no perfect solution, so there are no ideal test automation design patterns to use in all possible scenarios; we need to change and adapt depending on our test automation project. Some improvements will come from maintainability and reusability, but we will lose readability and understandability. So please decide wisely and combine them if necessary.
Happy Bug Hunting! – Enrique A. Decoss