Google Search Results
You arrived here after searching for the following phrases:
Click a phrase to jump to the first occurrence, or return to the search results.
This will be the first of many articles dealing with code design. What is code design? In object oriented languages it sometimes stands for deciding on the object class and structures needed to elegantly complete a task. For us it will just be a set of abstract principles that will allow us to write readable, maintainable code.
Among the QTP programmers community, many think that designing code is a waste of time. This view is common for many reasons, especially because QTP uses VBScript, which is a degraded programming language, and doesn’t support many of the prerequisites for code-design. However, some code-design core principles can be done in VBScript, and are the only way to keep our QTP code readable and maintainable over time.
Talking about code design concepts can be very confusing. Many of the concepts overlap, and sometimes one concept will be referred to by different names. I’m going to make things worse by merging some concepts, degrading and chopping parts of others, in order to make them more relevant to VBScript and QTP.
So, if you have any programming experience (especially with OO languages), you’ve probably heard these terms being used differently. Just remember I’m trying to keep my focus on QTP and VBScript, sometimes at the expense of accuracy (and this is only one of many articles that will expand and elaborate).
Today we’re going to deal with three main concepts: encapsulation, black-box coding and separation of concerns (SoC). These concepts have many synonyms, and sometimes overlap and refer to the same basic things, so demonstrating each of them separately is quite impossible. I’ll do a quick run through all of them, and then demonstrate them with a single test-case.
Keeping these principles in mind while writing our code will have an enormous impact on our future maintenance, code readability, bugs, and ability to add more features and functionality to out scripts. So pay attention :)
Separation of concerns
Is the best place to begin - it is simple, natural, and chances are you’re already doing it (to some degree). Simply put, it’s about breaking the code into as many logical pieces as you can, with as little overlapping as possible. This is usually done by separating long pieces of code into small dedicated functions. Later, the code looks like a composed puzzle, or a Lego combination of the different logical building blocks.
By packing each logical "block" separately, we achieve several goals:
Our code is much more readable
We can later maintain each of the blocks separately
The process can help us locate other places that use the same logical block, and turn the duplicate code into function calls
And many more benefits…
Without SoC, you have many different logical tasks intertwined throughout the code. If one of these tasks will need to change in the future, you’ll be force to fix the code in many different places, while risking messing up the other tasks’ functionality. With SoC, you can touch each logical task on its own, knowing that there’s no risk bugs will raise in different parts of your code.
So to illustrate: Dealing with warnings and error messages can be broken into three parts (actually more, but let’s go with 3):
Dealing with the message container
Recognizing the message
Reacting to it
If your code doesn’t separate recognizing the message from the actions needed to react to it, things can get very ugly very soon. What will happen when a new error message is introduced? Are you sure you know ALL the places that need updating? Can we really change the reactions without affecting message recognition? What if dealing with the new message requires performing actions in a different window? How will you construct a maintainable if switch?
Typically, in order to cover all our bases, we must put a hefty douse of duplicate code, or work very hard to tackle every little change in the future. Either way, it sucks, and our code will look terrible: it will be hard to tell which commands react to the error and which deal with the pop-up; it won’t be clear how the if-structure plays out; and the whole thing will barely be readable.
Black Box Coding
AKA concealing, decoupling or implementation hiding - is a concept that helps us to better build the separate building blocks we produced in order to keep our separation of concerns. The concept further deepens the separation and independents of the blocks, ensuring we could radically change any one of them, without any impact on the rest of the script.
The name actually says it all – the pieces of the script should operate as black-boxes. No one outside of the function or the action should know HOW it achieves its goal (its implementation), only how to tell her to do so. In a certain way this means we should "dumb down" the script, so it just calls all the smart folks to do the actual work for it. Actually, even these folks are dumb, and call other folks to do some of the work for them.
Without back-box coding, changes in one function will require changes is many other places in the code (because they’re exposed to the function’s implementation). Because it’s impossible to remember all the places that are relevant for a code-fix, we will usually end up with many small bugs that will either fail, or mess around with our script.
Unfortunately, no matter how hard we try, VBScript forces us to changes our code when the function’s signature (parameters) change - more on that here. But other than that, you should strongly avoid any kind of dependencies between different black-boxes in the script.
For example: Writing to the log
Bad way: insert Reporter.ReportEvent commands straight into the code
This exposes the implementation of the log-reporting, which means it will be impossible to change the means of reporting without changing each and every reporting command throughout the entire script. This is what we call strong-coupling – the log-reporting functionality is coupled with other parts of the script in an inseparable way.
Good Way: wrap the Reporter command with a custom function, and call it from the script:
Function QTPReport(iStatus, sHeader, sContent)
Reporter.ReportEvent iStatus, sHeader, sContent
End Function
Here we’ve decoupled the logging into a black-box. No one knows HOW it logs, but whenever the script needs logging services, it just calls the black box, knowing that it will get the job done. If we’d like, we could switch to excel logging by changing this function only, and the rest of the script will require zero-maintenance.
Encapsulation
Is all about gathering all the code that’s responsible for a specific task under one roof. The concept biggest benefit is eliminating any duplicate code in our scripts, but there are many welcomed side-effects. When there’s only one "parent" responsible for each task, the code becomes much clearer, and easier to maintain and expand.
When our logical functionality is properly encapsulated, there are no "I hope we fixed all the places that did X", because X is done in one place only. When you’ll find a bug, it will usually take you less then two seconds to find the exact place to fix it. Expanding functionality is done decisively and quickly, in case where it would be considered impossible in the past.
Summery
I hope you’ve got the feeling that these concepts overlap and complete each other. When you separate concerns, you’ll be doing so in black-boxes. And when you’re building a black-box, it will be properly encapsulated. And you can’t encapsulate anything without separating it from other logical functionality (SoC), and making it into a black-box. Well, you got the point.
The bottom line is writing maintainable code, by reducing the dependencies between the different parts. This way we can expand, fix, and maintain each part, without having to worry about the rest of the script. These concepts are so basic; the almost any technological design has to take them into account – in order to make a car maintainable, you got to make sure that changing a light bulb won’t affect the steering. We’ll see how these and other concepts play their parts in the coming articles.
Example
To make these concepts more tangible, here is an example of an error handling approach which utilizes these concepts. The example makes use of DP and dictionary objects, so make sure you know them well enough. Of course this isn’t the best way to deal with error messages, but it would do to demonstrate these concepts.
Download the example-app, cut and paste the code to QTP, and run it to see the results (don’t forget to change the AppPath constant at the top!).
The script will open the app, initiate an error window, and send it to the central function dealing with errors. The function will use its delegate-functions to extract the error from the window; analyze it and determine the needed actions to resolve it; and execute these actions.
Notice that if a new error message would be introduced (or an old one to be changed, we will only need to add a recognition block to the analyzer function (or edit an existing block), and within that block, add needed actions to be executed.
As with many of my examples, I try to introduce new ideas and concepts, beside those specifically discussed in the article. In this example you’ll find, among other things, using strings to represent action instructions, and using funcitons as factories that process these instructions and executes them.
'Where the app. file was saved
Const AppPath = "c:\" ‘Dont forget to change me!
'Gets the error message
Function ExtractErrorMessage(oErrorWindow)
Dim sMessage
Dim sWindowType
'we’re assuming we can tell the windows apart by the window class.
'More conditions and cases could be added
sWindowType = oErrorWindow.GetROProperty("micclass")
Select Case sWindowType
Case "Dialog"
sMessage = oErrorWindow.Static("micclass:=static").GetROProperty("text")
Case "VbWindow"
sMessage = oErrorWindow.VbLabel("vbname:=Label1″).GetROProperty("text")
'More cases go here
End Select
ExtractErrorMessage = sMessage
End Function
'analyzes the message, and spits out a collection of needed actions to resolve it
'Very complex action instructions can be created via nested dictionaries
Function AnalyzeMessage(sMessage)
Dim oNeededActions
Dim bFound
'The actions collection
Set oNeededActions = CreateObject("Scripting.Dictionary")
bFound = False ' Keep searching for a match?
'Recognition Block
If (instr(sMessage, "Index was outside the bounds") > 0) Then
bFound = True
oNeededActions.Add "Log", "Warning : " & sMessage & vbcrlf & "Tring to proceed"
oNeededActions.Add "Close Error Window", "No Data" ‘Just to demonstrate
End If
'Recognition Block
If (bFound = False) And (instr(sMessage, "Null") > 0) Then
bFound = True
oNeededActions.Add "Log", "Error: " & sMessage & vbcrlf & _
"Killing app and restarting"
oNeededActions.Add "Kill App", "Code Design.exe"
oNeededActions.Add "Open App", AppPath & "Code Design.exe"
oNeededActions.Add "Press", "Trigger 1″
'In real life, the data could be the QTP ref to the button
End If
' More Recognition Block
'Can use regular expression, complex If structures, etc.
oNeededActions.add "Result", bFound 'Did we manage to analyze the error?
Set AnalyzeMessage = oNeededActions
End Function
'Just an example of a function that executes commands
'More advanced uses could be achieved via Execute command
Function ExecuteNeededActions(oNeededActions, oErrorWindow)
Dim bResult
Dim i
Dim arrActions
Dim arrData
bResult = True
'See QTP help on Dictionary Objects for these lines
arrActions = oNeededActions.Keys 'The names of the actions - array
arrData = oNeededActions.Items 'The data of the actions - array
For i = 0 to uBound(arrActions) 'Loop through actions
Select Case arrActions(i)
Case "Kill App"
SystemUtil.CloseProcessByName arrData (i)
Case "Log"
Reporter.ReportEvent micWarning, "Got error message", arrData(i)
Case "Open App"
SystemUtil.Run arrData(i)
wait 1
Case "Close Error Window"
oErrorWindow.Type MicEsc
Case "Press"
VBWindow("vbname:=AdvancedQTP_Code_Design")._
VBButton("text:=" & arrData(i)).Click
'Just for the kicks of it, rerun the whole thing for the second message
DealWithError(VBWindow("vbname:=AdvancedQTP_Code_Design")._
Dialog("micclass:=Dialog"))
End Select
Next
ExecuteNeededActions = bResult
End Function
'The main function for dealing with errors
'An error could raise in different windows, all will be sent here
Function DealWithError(oErrorWindow)
Dim sMessage
Dim bResult
Dim oNeededActions
bResult = True
'get the message
sMessage = ExtractErrorMessage(oErrorWindow)
If sMessage = "" Then
Reporter.ReportEvent micFail, "Failed to get message", "Aborting"
DealWithError = False
Exit Function
End If
'Determine needed actions
Set oNeededActions = AnalyzeMessage(sMessage)
If oNeededActions("Result") = False Then
Reporter.ReportEvent micFail, "Coud not analyze message", sMessage
DealWithError = False
Exit Function
End If
'Execute the actions
bResult = ExecuteNeededActions(oNeededActions, oErrorWindow)
DealWithError = bResult
End Function
'Open App
SystemUtil.Run AppPath & "Code Design.exe"
wait 1
'Initiate Error
VBWindow("vbname:=AdvancedQTP_Code_Design").VBButton("text:=Trigger 2″).Click
'Lets assume a recovery scenario has catched the window, and sent it to our funciton
DealWithError(VBWindow("vbname:=ErrorMessage", "index:=0″))
Posted in Code Design

Yaron Assa




March 29th, 2008 at 7:47 am
[…] heavily on your familiarity with basic code design concepts and approach. Make sure you read the introduction to code design before […]