Design patterns in Automated testing

Design patterns are really important in software development. They help to seriously improve maintainability and readability of the code. Design patterns are solutions to general problems that software developers faced during software development. These solutions were obtained by trial and error by numerous software developers for a long period of time. So when we automate out tests we should also use best practices and appropriate design patterns.

Here is a small overview of different design patterns that could be really helpful to use in test automation. Actual implementation could differ from one programming language to another but the principle will be the same.

Page Objects pattern

This is the most important pattern related to software automation. The point is to wrap the pages or page fragments of a web app into objects. Elements are separated from tests logic. The purpose of a page object is to allow a software client to do anything and see anything that a human can by abstracting away the underlying HTML actions needed to access and manipulate the page. This pattern makes code much more maintainable and reusable. Page objects design pattern

Example: let’s imagine that you have several tests cases where a user should sign in. And in each automated test you have a code like this

fill_in 'user[login]', with: login
fill_in 'user[password]', with: password
click_button 'Sign in'

Then developers update some code and selectors for login and password data are now 'login' and 'password'. So you should update them in each test case manually.

Instead of it, you can create a page object for Sign in page and there a method for signing in:

module SignInPage
  def sign_in_as(login, password)
    visit '/sign_in'    
    fill_in 'login', with: login
    fill_in 'password', with: password
    click_button 'Sign in'    
  end
end

This approach helps to update all selectors quickly and wrap all internal page logic into a separate object. Then in code, you just simply call sign_in_as('Michael', '1234') when you want some user to sign in.

One of the frameworks that I used: https://github.com/natritmeyer/site_prism

It has great examples and it is quite simple to implement. But there are a lot of different frameworks for different programming languages, you just have to give them a chance.

Sections

Sometimes different pages have similar elements. For instance, a website has the same search field on the catalog page and on the product page. So instead of duplicating search field code in both page objects, it makes sense to create a section SearchField for this element and just include it into all page objects with the same element.

Factory pattern

Factory pattern is used to create objects based on specific rules, usually when we need to create an object from one of several possible classes that share a common superclass/implements an interface. It creates objects concealing the instantiation logic from the user. The best example is – WebDriver. As all these Chrome/Firefox/Safari/IE driver concrete classes implement this WebDriver interface, we are able to refer to the ChromeDriver/FirefoxDriver etc, instance through the WebDriver interface without much change in the code.

Example: When using Siteprism framework from the example above, to open a page you have to do something like this:

@home_page = Home.new
@home_page.load
@home_page.sign_in_as('Michael', '1234')

But what if you don’t want to repeat it each time you have to visit a page and do something like this:

visit_page('HomePage') do |page|
  page.sign_in_as('Michael', '1234')
end

Then you can create a Page Factory that would create a page object that you want. The code example is from here was slightly changed:

module Pages
  module PageFactory
    def visit_page(name, args = {}, &block)
      build_page(name).tap do |page|
        page.load(args)
        block&.call page
      end
    end

    def on_page(name, args = {}, &block)
      build_page(name).tap do |page|
        block&.call page
      end
    end

    def build_page(name)
      name = name.to_s
      Object.const_get("Pages::#{ name }").new
    end
end

Strategy Pattern

Strategy Pattern helps to define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it. That is, Strategy Pattern is useful when we have multiple algorithms for a specific task and we want our application to be flexible to choose any of the algorithms at runtime for a specific task.

Example: We can add an item to cart using UI web form and on the other hand we can use API. So for some tests, it is crucial to check all web elements and UI, but for other tests, we don’t want to waste our time opening page, clicking on buttons, then adding another item and so on, we just want to add a lot of products to the cart.

This pattern allows you to implement different algorithms and choose any of them depending on what do you want to test.

Sum up

Basically, these are the most popular and well-tested solutions. There is a bunch of different approaches that could help with specific problems. But you have to bear in mind that design patterns aren’t really mandatory. When you learn them though, you would know exactly when to use.

Leave a Reply

Your email address will not be published. Required fields are marked *