Skip to content
Go back

Test Automation - Improve Code Readability Using The Builder Pattern

Published:

Introduction

In this article, I will review the process of improving our testing code readability using the builder pattern. This pattern offers multiple methods that can be used to gradually define the characteristics of the type to be created.

I will show in this article how to improve a login method from passing two strings “username” and “password” to passing a user object and finally implementing the builder pattern to create the user object.

Starting Point — Login using a method that accepts two String parameters

The starting point contains a login page object class that includes login web elements and a login method and a class that contains a test method that uses this page object to log in to our system and assert that we are logged in.

public class LoginPage {
    public WebDriver driver;

    public LoginPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    @FindBy(id = "username")
    private WebElement userName;

    @FindBy(id = "password")
    private WebElement userPassword;

    public void loginAs(String name, String password) {
        userName.clear();
        userPassword.clear();
        userName.sendKeys(name);
        userPassword.sendKeys(password);
        userPassword.sendKeys(Keys.ENTER);
    }
}

This is our test method:

@Test(description = "valid Login")
public void validLogin(String baseUrl) throws Exception {
    LoginPage loginPage = new LoginPage(driver);
    loginPage.loginAs("test@test.com", "Test");
    assertThat(driver.getCurrentUrl()).isEqualTo(baseUrl + "/secure");
}

Looking at the login method, it’s not clear what we are passing to this method to log in, without the test description, it is hard to tell without running this test.

Starting to improve - Log in Using a user object

We can improve the readability by using a user object, let’s do that.

First, we need to create a user class, which is a blueprint for creating our user object.

@Data
public class User {

    private String name;
    private String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }
}

We need to alter the login method to accept a user object.

public void loginAs(User user) {
    userName.clear();
    userPassword.clear();
    userName.sendKeys(user.getName());
    userPassword.sendKeys(user.getPassword());
    userPassword.sendKeys(Keys.ENTER);
}

As you can see, our test case, readability is improved.

@Test(description = "valid Login")
public void validLogin(String baseUrl) throws Exception {
    User validUser = new User("test@test.com", "Test");
    LoginPage loginPage = new LoginPage(driver);
    loginPage.loginAs(validUser);
    assertThat(driver.getCurrentUrl()).isEqualTo(baseUrl + "/secure");
}

We can understand that we are logging in as a valid user, but it is unclear which parameters are used to create the user object.

Don’t worry, the builder pattern comes to our rescue.

Finishing to improve Test readability - Using the builder pattern

First, a short explanation about the pattern taken from here:

In Object-Oriented Programming (OOP) we spend most of our time creating objects or instances of classes. We usually prefer constructors when we create these objects. However, increasing the number of fields leads us to think differently because of the exponential and unnecessary complexity of the constructors.*Builder pattern,*on the other hand,overcomes this issue by using a builder to create an object in a step by step manner.

Let’s see the builder pattern in action when you want to create a user. We are using lombok projects @Data and @Accessors annotations to generate getters and setters and to chain them until the object creation - this is known as a fluent interface.

@Data
public class User {
    private String name;
    private String password;

    @Data
    public static class Builder {
        @Accessors(chain = true)
        private String name;
        @Accessors(chain = true)
        private String password;

        public User create() {
            User user = new User();
            user.name = this.name;
            user.password = this.password;
            return user;
        }
    }

    private User() {
        // Constructor is private.
    }
}

Let’s refactor our login method the use our user builder.

@Test(description = "valid Login")
public void validLogin(String baseUrl) throws Exception {
    User validUser = new User
        .Builder()
        .setName("test@test.com")
        .setPassword("Test")
        .create();

    LoginPage loginPage = new LoginPage(driver);
    loginPage.loginAs(validUser);
    assertThat(driver.getCurrentUrl()).isEqualTo(baseUrl + "/secure");
}

Our test case readability is now improved, the creation of the user object is more elegant, and we have a more readable code - we set the user parameters one by one meaningfully.

In conclusion

In this article, we reviewed the builder design pattern and its advantages.

Further reading on Java design patterns can be found in this link.

Happy testing!


Suggest Changes

Have a challenge? Let's Talk


Previous Post
Test Automation - How To Dockerize Our API Testing
Next Post
Test Automation - Networking with Java and PowerShell