preload

Peeling an Onion Code

Posted by Albert Gareev on Apr 06, 2010 | Categories: ImplementationNotes

Peeling an Onion Code

In my previous post I presented code structure looking like layers of an onion. In this post I’m exploring ways to transform it to structures that have much less drawback while performing same (or richer) functionalities.

Let’s begin from the very first issue: readability of an Onion Code.

Generally speaking, test steps are sequential. Even if they depend on each other they don’t have to be wrapped around each other. The following code demonstrates how all the layers could be rewritten in a step-by-step manner.

As you will see our code is not a “matryoshka” anymore.


Do While True

 If Not Browser("title:=Google.*").Page("title:=Google.*").Exist Then
  boolStatus = False
  sMessage = "Failed to find Google Search Page"
  Exit Do
 End If

 If Not Browser("title:=Google.*").Page("title:=Google.*").WebEdit("name:=q").Exist Then
  boolStatus = False
  sMessage = "Failed to find input box on Google Search Page"
  Exit Do
 End If

 If Not Browser("title:=Google.*").Page("title:=Google.*").WebEdit("name:=q").CheckProperty("disabled", 0) Then
  boolStatus = False
  sMessage = "Input box is disabled; couldn't type search query"
  Exit Do
 End If

 Browser("title:=Google.*").Page("title:=Google.*").WebEdit("name:=q").Set "test"

 If Not Browser("title:=Google.*").Page("title:=Google.*").WebButton("name:=Google Search").Exist Then
  boolStatus = False
  sMessage = "Failed to find Google Search button"
  Exit Do
 End If

 Browser("title:=Google.*").Page("title:=Google.*").WebButton("name:=Google Search").Click

 boolStatus = True
 Exit Do

Loop

If boolStatus Then
 Reporter.ReportEvent micPass, "Google Search", "Search query submitted"
Else
 Reporter.ReportEvent micFail, "Google Search", sMessage
End If

The code above performs a couple of single atomic test steps. If written as manual testing script, they would look someting like the table below.

Object Action Expected result
Input box Type in “test” Entered text appeared
“Google Search” button Mouse click, single The button was pressed

 

Note, while testing manually we would perform much more than that. And the role of test automation is to serve testing needs. Therefore, any test step implementation should implicitly include service functionalities that verify context, gather information about test object, and automatically log it. The following sequence lists functionalities applicable to GUI interaction test step.

  1. locate GUI context (web page, dialog form, window, etc.) 
  2.   [log details if failed to do that]

  3. locate GUI object
  4. [log details if failed to do that]

  5. Input to or Retrieve information from GUI object
  6. [log details if failed to do that]
    [log details if successfully did that]

Note that steps 2,3 are generic and do not depend on test logic or business domain.

Let’s rewrite a test step now in accordance to the sequence defined.


Public Function Button_Input(ByRef objButton, ByVal sStep)

   If Not objButton.Exist Then
       Reporter.ReportEvent micFail, sStep, "Failed to find the button"
       Button_Input = False
       Exit Function
   End If

   If Not objButton.CheckProperty("disabled", 0) Then
       Reporter.ReportEvent micFail, sStep, "The button is disabled"
       Button_Input = False
       Exit Function
   End If

   objButton.Click
   Reporter.ReportEvent micDone, sStep, "Pressed the button"
   Button_Input = True
End Function

A side note. If you’re using these code examples for your practical work you may want to consider getting familiar with another approach to function wrapping first.

I skip here providing of source code of Set_Context and Edit_Input functions.

Now, when we have the all atomic test steps written as generic functions the original code block becomes much more compact.

boolStatus = True
Do While True
 Set objGUIContext = Browser("title:=Google.*").Page("title:=Google.*")
 boolStatus = Set_Context(objGUIContext, "Google Search")

 If Not boolStatus Then Exit Do
 '
 boolRC = Edit_Input(objGUIContext.WebEdit("name:=q"), "Google Search", "test")
 If Not boolRC Then boolStatus = False
 '
 boolRC = Button_Input(objGUIContext.WebEdit("name:=q"), "Google Search")
 boolStatus = boolStatus AND boolRC
 '
 Exit Do

Loop
'

If boolStatus Then
 Reporter.ReportEvent micPass, "Google Search", "Successfully executed test steps"
Else
 Reporter.ReportEvent micFail, "Google Search", "Failed to execute test steps"
End If

As you can see the code is not intended to check any single static requirement although it automatically performs many verification steps along the way. The code is intended to “travel” a certain pathway in the application, and collect information about process and application status.

As much as passed manual testing only tells that no problems were noticed but does not guarantee they do not exist, the testing code only reports success (or failure) of steps performed not requirements verified.

Note that the code is not designed to proceed if a testing context is not available but we may still want to collect as much information as possible within it – even if some testing steps were failed. This will give more details for investigation afterwards.

Another advantage is that we can now keep test logic and generic functions separately. Maintenance debt is significantly reduced.

By the way, it doesn’t mean we reduced testing time. We saved time on checking and now can do more exploration.


  • One response to "Peeling an Onion Code"

  • QTP geek
    9th April 2010 at 23:44

    found your site on del.icio.us today and really liked it…

Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported
This work by Albert Gareev is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported.