Peeling an Onion Code
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.
- locate GUI context (web page, dialog form, window, etc.)
- locate GUI object
- Input to or Retrieve information from GUI object
[log details if failed to do that]
[log details if failed to do that]
[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"
found your site on del.icio.us today and really liked it…