Refactoring

The article Refactoring Automated Functional Test Scripts with TestWise introduces the ‘page objects’ and covers the refactoring step by step. It is highly recommended to read it or the Practical Web Test Automation book first.

Refactoring has been widely used in programming, and its practices are regarded as one of the most important advancements in software development in recent decades. Test refactoring applies the code refactoring principals to test scripts. Now with TestWise, you can perform test refactorings with ease. For users with programming knowledge, some of the practices may be familiar; For those who haven’t had programming experiences, don’t worry, it is easier than your thought, with TestWise.

Extract to Function

Motive
There are test steps that are performing common operations such as Log In, and User Sign Up. You want a simple way to reuse them.
Outcome
The selected test steps are extracted to a function and moved to a helper file, where they can be shared by all test cases.
Benefits
More readable scripts; Less duplication; Test scripts reuse.

  Steps:

  1. Identify the test steps

    For example, here is a test case containing user login test steps.

    it "[124] Sample test case name" do
      # steps before ...
      enter_text('username', 'john')
      enter_text('password', 'foobar')
      click_button('Login')
      # steps after ...
    end
    

    I want to extract three login steps (starting with enter_text('username', ...) into a reusable function.
     

  2. Select the identified test steps

    If there is only one test step, then you may skip this step. TestWise will take the current line as selected test step.
     

  3. Invoke the refactoring

    Select the Extract to Function menu under ‘Refactor’ Menu or use the shortcut key (Ctrl + Alt + M).

     
     

  4. Enter function name in the Extract Function dialog

     

    You will get:

    def login
      enter_text('username', 'john')
      enter_text('password', 'foobar')
      click_button('Login')
    end
    
    it "[124] Sample test case name" do
      # steps before ...
      login
      # steps after ...
    end
    

Move to Helper

Motive
You want a locally defined function in a test script to be accesible by all test scripts
Outcome
The function defintion is moved to test_helper.rb
Benefits
The function is now reusable for all test scripts, possible auto completion if IDE supports

 
Steps:

  1. Move caret to the function name and invoke the refactoring

     

    Select Extract to Function menu under ‘Refactor’ Menu or use the shortcut key (Ctrl + Alt + H).
     

  2. Confirm

     

    Click OK button, the login function will be moved to test_helpe.rb, if you follow TestWise convention.

    # Now in test_helper.rb
    def login
      enter_text('username', 'john')
      enter_text('password', 'foobar')
      click_button('Login')
    end
    

Now this function is available to all test scripts. For example, to use it in a new test, type lo then press Ctrl + Space, which will show matching helper functions for you to select.

 

Move

Motive
You use the same set beginning or ending test steps in multiple test cases.
Outcome
Move the common test steps into the shared sections that will run before/after each test case.
Benefits
Script reuse, concise and show the purpose of the test case better.

 
Steps:

  1. Select the common steps

    The steps are common to multiple test cases in the same test script file, just select one instance of them.
     

  2. Invoke refactoring

    Either from Move under Refactor, or shortcut key (Shift + F7).

     
     

  3. Select target

    A context menu will be shown (next to the selected test steps), choose the target scope.

     
     

  4. Preview the refactoring

    Preview the change to the target, and check “Replace occurences in other test cases” checkbox if it applies to more than one test cases.

     

Here is what the test script looks like after the refactoring:

 

Extract to Page Function

Motive
You have recorded or entered some test steps, and noticed duplications. As time goes by there will be more duplications which will lead to hard-to-maintain test scripts.
Outcome
The selected test steps are extracted to a function in a page class which will be shared by all the test scripts. The page class provides the context, which makes maintenance easier.
Benefits
More readable code by expressing operations in DSL Test Scripts Reuse; More immune to application changes; Enable other convenient operations to auto-complete in TestWise.

 
Steps:

  1. Identify the test steps on a web page

    Quite often, we hear people saying something like “On homepage, click sign up link to get to the signup page, then enter preferred login …”.

    In this example, we will try to extract operations to SignUpPage. In the sample script below, line 3 to line 8 are operations on the sign up web page.

    it "[124] signup a new user" do
      logout # a function
      click_link 'Signup'
      enter_text("login", "John")
      enter_text("password", "foo")
      enter_text("passwordConfirmation", "foo")
      select_option("country", "Australia")
      click_button("Submit")
      # steps after ...
    end
    
  2. Move the caret to first operation on wanted page

    In this case, it is enter_text(“login”, “John”).
     

  3. Invoke ‘Extract Page’ refactoring

    Either from the menu

     

    Or use shortcut key (Ctrl + Alt + G).
     

  4. Enter the page and operation names

    It is like filling “on ___ page do ___”. In our example, we fill ‘SignUpPage’ and ‘enter_login’.

     

    Please note the convention here, Camel Case for page names, and lower case (joined with _) as operation (or function) names.

    Also I added a function parameter username to replace the text “John”, this will provide more flexibility for the test scripts.

    The test script is now changed to:

    it "[124] signup a new user" do
        logout # a function
        click_link 'Signup'
        sign_up_page = SignUpPage.new(driver)
        sign_up_page.enter_login("John")
        enter_text("password", "foo")
        # more ...
    end
    

    And a new file sign_up_page.rb is created under pages folder with the following content:

    require File.join(File.dirname(__FILE__), "abstract_page.rb")
    
    class SignUpPage< AbstractPage
    
     def initialize(driver)
       super(driver, "") # <= TEXT UNIQUE TO THIS PAGE
     end
    
     def enter_login(username)
       enter_text("login", username)
     end
    end
    

    Rerun the test case.
     

  5. Continue refactorings to remaining operations

    The complete refactored test case:

    it "[124] signup a new user" do
        logout # a function
        click_link 'Signup'
        sign_up_page = expect_page SignUpPage
        sign_up_page.enter_login('john')
        sign_up_page.enter_password('foo')
        sign_up_page.select_country('Australia')
        sign_up_page.submit
        # steps after ...
    end
    

    In SignUpPage (file: sign_up_page.rb)

    class SignUpPage< AbstractPage
    
      def initialize(driver)
       super(driver, "") # <= TEXT UNIQUE TO THIS PAGE
      end
    
      def enter_login(username)
       enter_text("login", username)
      end
       
      def enter_password(pwd)
        enter_text("password", pwd)
        enter_text("passwordConfirmation", pwd)
      end
         
      def select_country(country)
        select_option("country", country)
      end
         
      def submit
        click_button("Submit")
      end
    end
    

Introduce Page Object

Motive
You want to use one of already defined page classes in test script
Outcome
A new page object is declared in the test script
Benefits
Efficient, page class discovery and avoid typos

 
Steps:

  1. Type ep followed by a Tab in a test script

    A list of defined page class name will be shown in a pop up for selection.

     
     

  2. Select one page class.

    A page object is declared in the test script, flight_page = FlightPage.new(driver).

Copy Page File

Motive
You want to create a similar page class like an existing one
Outcome
A new page class is created with same content, but with correct class name
Benefits
Efficient

 
Steps:

  1. Open an existing page file

    The page you want to copy from.
     

  2. Invoke the refactoring

     

    Either from the ‘Refactor’ menu or simply press Shift + F5.

     
     

  3. Enter new file’s name

     

    A new page (user_profile_page.rb) is created with the content from the copied page, except the page name which is based on user-entered new page file.

Rename

Motive
The terminology or application changes make names used in existing test cases incorrect or lead to misunderstanding. Or just find a better name
Outcome
The terms used in test cases are up to date with your domain language
Benefits
More readable test cases; No error-prone ‘search and replace’ in the whole projects

This refactoring can also be further classified into the following sub ones.

  • Rename Page Class
  • Rename Function Name
  • Rename Local Variable
  • Rename Instance Variable
  • Rename Global Variable

 
Steps:

  1. Identify a function name, class name, variable name

    Just move the caret to the name you wish to change.
     

  2. Invoke the refactoring

    Either from the ‘Refactor’ menu or simply press Shift + F6. TestWise will analyse the script and choose appropriate sub-refactoring.

       

  3. Enter the new desired name and apply

     

    Depending on the sub-refactoring, changes will be made accordingly.

    Rename Page Class
    Page Class declaration and all appearances of the page class name.
    Rename Function
    Function declaration and all references to that function.
    Rename Local Variable
    All appearance of variable in that scope.
    Rename Instance Variable
    All appearances of the instance variable (like @username) in current file.
    Rename Global Variable
    All appearances of the global variable (like $SERVER_NAME) in all project files.

    Here is what looks like after renaming a page object variable in one test case: