When builder comes in test automation

While playing around with different design patterns in order to increase the efficiency of building a maintainable automation related architecture I’ve decided to give a chance also to the builder pattern. The main idea of it is to separate the construction of a test from its representation so the builder will implement the whole test logic, and a manager will provide to the user the representation. Like in real life right? (Developers implement features and a manager is praising).

Following this approach, you can create simple and readable tests, keeping at the same time the implementation logic structured and easy to maintain.

Implementation

Step 1 – Builder interface

The first step to be done is the creation of an abstract interface used to define the product (test) components. We want our tests to have a prior validation, going further the test flow, a post validation, the reports, and the quit tests method. Apart from them we will add some logic in here related to the driver instantiation, a logic which will be used across all concrete builders.

class Builder(metaclass=abc.ABCMeta):
    """
    Abstract interface for defining parts of the Product
    """

    def __init__(self):
        self._driver = get_selenium_driver('chrome')
        self.product = Test()
        self._driver.get('https://en.wikipedia.org/')

    @abc.abstractmethod
    def get_pre_validation(self):
        pass

    @abc.abstractmethod
    def get_flow(self):
        pass

    @abc.abstractmethod
    def get_post_validation(self):
        pass

    @abc.abstractmethod
    def get_report(self, status):
        pass

    def close(self):
        self._driver.quit()

Concrete Builder

Here a test flow example will implement the Abstract Builder Interface methods. If we want other flows we just have to follow the same pattern, and change the methods implementation accordingly.

class SearchFlow(Builder):
    """
    Implementation of a concrete builder
    """
    SEARCH_CONTAINER = 'searchInput'
    SEARCH_BUTTON = 'searchButton'
    CREATE_ACCOUNT = 'pt-createaccount'
    LOGIN = 'pt-login'
    HEADING = 'firstHeading'

    def get_pre_validation(self):
        """
        Validate if UI is available for the environment to be tested
        :return: True or False
        """
        return self._driver.find_element_by_id('p-search').is_displayed()

    def get_flow(self):
        """

        :return: True if validation succeeded
        """
        self._driver.find_element_by_id(SearchFlow.SEARCH_CONTAINER).send_keys('Design patterns')
        self._driver.find_element_by_id(SearchFlow.SEARCH_BUTTON).click()

    def get_post_validation(self):
        """

        :return: True if flow was executed
        """
        return self._driver.find_element_by_id(SearchFlow.HEADING).text == 'Design pattern'

    def get_report(self, status):
        print("Test execution:", status)

Step 2 – The Manager

One of the key points in builder pattern definition is the Manager or  Director or whatever you want to call it. It’s actually a class which will delegate the implementation details to the builders and provides the final product (in our case the test) to a client.

class TestManager:
    """
    Control the construction process, having a builder associated with it
    It delegates to the builder the implementation details,
    and deliver the already implemented functionality to the client
    """

    __builder = None
    __status = True

    def set_manager(self, builder):
        """
        Selection of the builder which will construct the product
        :param builder: the concrete builder 
        """

        self.__builder = builder

    def get_test(self):
        """
        Prepare the test product to be delivered to a client
        """
        
        # Pre validation
        pre_validation = self.__builder.get_pre_validation()
        # Set flow
        if pre_validation:
            self.__builder.get_flow()
            # Make comparison
            if self.__builder.get_post_validation():
                self.__status = True
            else:
                self.__status = False
        else:
            self.__status = False
        # Post results
        self.__builder.get_report(self.__status)
        # Close driver
        self.__builder.close()

Step 3 – Testing

Now that we are done with the implementation, let’s check the builder. Following the unit tests approach, we’ve declared a test_flow method, where we just have to instantiate the concrete builder (Searchflow), the manager (TestManager), send the builder to the manager and in the end our manager will provide the final test. Easy right?

 class TestSearchFlow(TestTemplate):
    """
    Test class for testing a search on Wikipedia
    """

    def test_flow(self):
        """
        Test Steps
        """
        home_builder = SearchFlow()
        manager = TestManager()
        manager.set_manager(home_builder)
        manager.get_test()

Step 4 – Conclusions

As an endpoint, yes, it makes sense to use the builder pattern when designing an automation framework. You will have two big advantages: readable tests, maintainable implementation. The only downside which I found using this approach is that you could still have some code duplication while implementing the get_flow method.Either way, this could be solved by combining this pattern with the classical page object.

More details about the implementation could be found here

2 thoughts on “When builder comes in test automation

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.