Why interfaces are used in test automation frameworks?
Karthikeyan Rajendran
Selenium 3&4 | Spring Boot | Playwright | Cypress | Postman | RestAssured | Supertest | Restful API | GraphQL | Contract Testing | Micro-service | Gatling | K6 | Locust | JMeter | Performance Engineering | SQL
Back Story:
One day at the cafeteria, a colleague and I casually discussed the framework we designed. He raised a question: 'Why are interfaces used in test automation frameworks?' Inspired by this conversation, I decided to turn it into an article that could be helpful to others. I wrote this article based on my experience and perspective, and I warmly welcome any thoughts and ideas.
General Definition:
An Interface in programming is a contract or a blueprint for a class. It defines a set of methods that a class must implement. An interface specifies the behaviour that a class should adhere to without specifying how it should be implemented.
How does Interface help test the automation framework?
In the context of test automation frameworks, interfaces define a common set of methods or operations that various classes or components of the framework should support. Here are a few reasons why interfaces are used in test automation frameworks:
1. Encapsulation and Abstraction: Interfaces help encapsulate the implementation details of classes and provide a way to interact with them through a well-defined interface. They abstract away the implementation complexities and provide a simplified view of the functionalities offered by the framework.
2. Modularity and Reusability: By using interfaces, you can define reusable components that can be easily plugged into the framework. This promotes modularity and allows for a flexible composition of different components to create custom test automation solutions.
3. Standardization and Interoperability: Interfaces provide a standard way to define and communicate the contract between different components of the framework. This enables better collaboration among team members and promotes interoperability between different modules of the framework.
4. Flexibility and Extensibility: Interfaces allow for flexible and extensible designs by providing a mechanism to add new functionality or change the behaviour of components without impacting the existing codebase. New classes can be created that implement the interface, enabling seamless integration into the existing framework.
5. Testability: Interfaces make it easier to write test cases and perform unit testing. With interfaces, you can create mock objects or stubs that simulate the behaviour of real objects during testing, allowing for isolated testing of individual components.
Overall, interfaces in test automation frameworks facilitate a modular, flexible, and maintainable approach to designing and implementing automation solutions. They promote code reusability, simplify collaboration, and enhance the overall robustness and scalability of the framework.
领英推荐
SOLID Principle Consideration
Apart from the above-mentioned information, the reason for implementing the interface in our interface is to achieve one of the SOLID principles, which is the Dependency Inversion Principle(DIP).
According to the Dependency Inversion Principle (DIP), entities should depend on abstractions, not on concretions. This principle dictates that the high-level module must not depend on the low-level module; both should depend on abstractions. This approach keeps coupling low and facilitates easier changes to our design. DIP also enables the testing of components in isolation.
Consider the example below: we have a DomElementInteraction class that depends on the concrete SeleniumEngine class, thereby violating the Dependency Inversion Principle (DIP).
public class DomElementInteraction {
private SeleniumEngine selenium;
public Car(SeleniumEngine selenium) {
this.selenium = selenium;
}
public void type() {
selenium.fill()
}
}
public class SeleniumEngine {
public void fill() {...}
}
The code will work for now, but what if we want to add another engine type, such as a Playwright engine? This would necessitate refactoring the DomElementInteraction class. However, we can address this by introducing a layer of abstraction. Instead of DomElementInteraction depending directly on SeleniumEngine, let’s add an interface:
public interface RunnerEngine {
public void fill();
}
Now, we can connect any RunnerEngine type that implements the RunnerEngine interface to the DomElementInteraction class:
public class DomElementInteraction {
private RunnerEngine engine;
public Car(RunnerEngine engine) {
this.engine = engine;
}
public void start() {
engine.fill()
}
}
public class SeleniumlEngine implements Engine {
public void fill() {...}
}
public class PlaywrightEngine implements Engine {
public void fill() {...}
}
I welcome your valuable thoughts and views on the above content. Thank you!
Senior Managing Director
1 年Karthikeyan Rajendran Very informative. Thanks for sharing.