Site icon Vinsguru

Selenium WebDriver – Design Patterns in Test Automation – Proxy Pattern

Overview:

As a software engineer, We all face some errors/exceptions while writing code! So what do we do when we face such a problem? If we are not sure, We google for solutions immediately. Don’t we? We google because we know that we would not be alone and someone would have already found the solution, for the problem we are facing now, which we could use to solve our problem.

Well, What to do if we face an issue in the high level software design – when connecting different classes / modules – when you have only a vague idea!! How can we google such things when we ourselves are not sure of the problem we have in the first place!!

No worries, You still have a solution!

Design patterns are the solutions for the problems which you might face in the software design!!

Design Patterns are well optimized and reusable solutions to a given problem in the software design. It helps us to show the relationship among the classes and the way in which they interact. Design pattern is a template which you have to carefully analyze and use it in appropriate places.

More information on design pattern can be found here.

Design Patterns in Test Automation:

As an automated test engineer, should we really care about design principles and patterns? Why do we need to learn/use Design Patterns in functional test automation?

After all, Test automation framework/automated test case is also a software which is used to test another software. So, we could apply the same design principles/patterns in our test automation design as well to come up with more elegant solution for any given design related problem in the test automation framework.

Remember that Design Patterns are NOT really mandatory. So you do not have to use them! But when you learn them, you would know exactly when to use! Basically these are all well tested solutions. So when you come across a problem while designing your framework/automated test cases, these design patterns could immediately help you by proving you a formula / template & saves us a lot of time and effort.

Note: Your aim should not be to implement a certain pattern in your framework. Instead, identify a problem in the framework and then recognize the pattern which could be used to solve the problem. Do not use any design pattern where it is not really required!!

In this article, We are going to see where we could use the Proxy Pattern which is one of the Structural design patterns.

Udemy – Selenium WebDriver and Design Patterns Course:

VinsGuru has released a brand new course in Udemy on Selenium WebDriver and Design Patterns. ~8 hours course with all the important design patterns to design better, reusable page object models, test classes etc. Please access the above link which gives you the special discount.  You can also get your money back if you do not like the course within 30 days.

Problem Statement:

I am going to consider the same problem mentioned in this article here. But We will try to implement the solution differently using design patterns.

Problem is – I run thousands of automated tests on a daily basis as part of our nightly regression. There are few cases which require DB access to execute certain SQL statements as part of data setup and cleanup activity. In the lower environments like Dev & QA , the script will work perfectly fine where we access to DB. We have a frequent PROD push – so we also our run automated regression on PROD after the release to ensure that PROD is working as expected. As we do not have PROD DB access, the script will throw exception as it would try to connect to a database. The team started writing a lot of it-else conditions everywhere in the test before executing DB statements to ensure that the SQL statements related steps are ignored in the test for PROD environment.

If-else conditions affect the readability of the tests. Secondly, If we need to add one more environment like Staging where we do not have DB access, then you need to update all the tests.

Proxy Pattern:

Proxy design pattern allows us to create a wrapper/proxy class over a real object. Then this proxy class is used to provide controlled access to the real object. It is a very simple solution to restrict access!  More info on the below UML diagram is here.

Lets see how we could use Proxy design pattern to solve our problem here. Lets first create an Interface with all the possible methods required for the DB data setup.

public interface DBUtil {
    
    void insertUser();
    void deleteUser();

}

I have an Enum for TestEnvironment.

public enum TestEnvironment{
    
    DEV,
    QA,
    STAGING,
    PROD
    
}

Lets create a real class which implements this interface! This class is responsible for connecting to the DB and executing the db statements.

class DBUtilImpl implements DBUtil {

    DBConnection dbConnection;

    public DBUtilImpl(TestEnvironment environment){
        //DB connection
    }

    @Override
    public void insertUser() {
        System.out.println("inserting new user");
    }

    @Override
    public void deleteUser() {
        System.out.println("deleting user");
    }
    
}

Now Lets create a proxy class which implements the same interface!  This class contains a list of all the test environments which do not support DB execution.

public class DBUtilProxy implements DBUtil {

    private DBUtil dbUtil;
    private static final List<TestEnvironment> restrictEnvironmentList;

    //restrict DB access for the below environments
    static{
        restrictEnvironmentList = new ArrayList<>();
        restrictEnvironmentList.add(TestEnvironment.PROD);
        restrictEnvironmentList.add(TestEnvironment.STAGING);
    }

    public DBUtilProxy(TestEnvironment testEnvironment){
        if(!restrictEnvironmentList.contains(testEnvironment)){
            dbUtil = new DBUtilImpl(testEnvironment);
        }
    }

    @Override
    public void insertUser() {
        if(Objects.nonNull(dbUtil)){
            dbUtil.insertUser();
        }
    }

    @Override
    public void deleteUser() {
        if(Objects.nonNull(dbUtil)){
            dbUtil.deleteUser();
        }
    }

}

My testNG/JUnit test class will look something like this. We always use the DBUtilProxy class to create an instance of DBUtil. We pass the test environment for which we need to make a DB connection. Proxy class decides whether to create DBUtilImpl object or not – depends on the environment. So, if any of the test environment does not support DB execution, then it is simply skipped.

public class Test1 {
    
    DBUtil dbUtil;
    
    @BeforeTest
    @Parameters
    public void dataSetup(String testEnvironment){
        dbUtil = new DBUtilProxy(TestEnvironment.valueOf(testEnvironment));
        dbUtil.insertUser();
    }

    @Test
    public void test1(){
        //login as the user
    }

    @Test
    public void test2(){
        //place order
    }
    
}

The implementation would look as shown in the below diagram.

Note:

Summary:

By using Proxy design pattern we could restrict access to an object as we saw above. It avoids if-else conditions everywhere in your script. We could use this pattern even during our page object design as well if you have any such functionality.

For ex: Consider this flow.

  1. User searches for a product from a e-comm site
  2. Selects a product to order
  3. Enter payment info [Usually fake CC for test environments / Not for production]
  4. Order Confirmation page

We test the above workflow thoroughly in the lower environments using a fake CC. But for production, fake CC might not work. Nobody will want to use their CC as test CC for PROD. So we might want to skip those steps from the execution. We could use Proxy page for to skip those pages where we do not need to test in PROD.

 

Happy Testing & Subscribe 🙂

 

Share This:

Exit mobile version