Showing posts with label code optimization. Show all posts
Showing posts with label code optimization. Show all posts

June 12, 2008

Option Explicit and other QTP curiosities

I like variable declaration and strong type casting. It makes sense, because it makes sense in assembly. And it prevents me from making mistakes in variable names. QTP offers variable declaration with the command "option explicit". This works just the same as in VBA or VB, but... you need to be consequent. Every action script, function library or external vbs file needs an option explicit command or else it won't work. You thought you didn't need it in that tiny class file where mistakes are not possible? You thought wrong. Ignoring one option explicit, makes all other option explicits obsolete and your scripts will be interpreted without the need for variable declaration.

By the way, undeclared variable detection only works during runtime. There is no pre-compile option as in VB or VBA, so it is good old trial and error when you want to debug your script on undeclared variables.

"Randomize" has sort of the same behavior: Don't put it in a header script or such kind, but put it in the library where you use the rnd function. If you use rnd in multiple libraries, you'll have to put Randomize in each of them, otherwise you are stuck with the same 'random' numbers each time you run the script.

June 3, 2008

Lowlevel Code Optimization in WinRunner


At some moment in your automated test project you enter the phase you are questioning yourself: "Weren't we automating the tests because it is fast? Why are my tests running so slow!?!". Then it is time to review your code and to optimize it a bit.

In this post, I want to discuss low level optimization. As WinRunner does not have a smart compiler (it is more an interpreted language then a compiled one), we have to do all optimizations by ourself.

1. Order of function calls in a condition
When you use and and or constructs in conditions ('if' statements for example), you have to think about the order:

Let's say, we have two checks, a time consuming (fncSlowCheck) and a fast one (fncFastCheck).
When you use an AND or an OR construct, you need to put the fastest compare first:

if (fncFastCheck() == E_OK && fncSlowCheck() == E_OK) { ... }

if (fncFastCheck() == E_OK || fncSlowCheck() == E_OK) { ... }

WinRunner will first evaluate the fast function. When this evaluates to false, the second function will not be called in case of the AND construction, because the total if statement can never become TRUE, and the if statement is stepped over.
With the OR, it is just the other way around. When the first function evaluates to TRUE, the second function will not be called.

This behavior is called lazy evaluation and is something you have to keep in mind when you use functions in conditional statements that can impact the application under test.

if (edit_set(myEdit1, "foo") != E_OK && edit_set(myEdit2, "bar") != E_OK) {
    write2report("Something went wrong setting myEdit1 and/or myEdit2", ERROR)
}

Besides this is a crappy way of reporting, the second edit will never be set as soon the first edit_set() evaluates to an error.

2. Use switch / case constructs. They are fast!
The reason why switch / case are fast is because jmp (jump) commands in assembly are faster than cmp (compare) commands used with each loop iteration. Combined with the so called "fall through" mechanism, they cannot be beaten by loops. For a detailed article, see Duff's device on wikipedia.

3. ++i is faster than i++
Make it a good habit to use ++{variable} in your for constructs:

for (i = 0 ; i < 100 ; ++i)

I absolutely noticed the difference since I had some large multi dimensional arrays I had to iterate through.
One note: Keep in mind the order of handling of the incrementation. Do not blindly search and replace all {variable}++ with ++{variable}, this will mess up your test.

4. Calls to external functions can be slow
When you use external functions in time critical processes, it is a good habit to check the performance of these functions. Once, I had external and() and or() functions (they are not provided by WinRunner) and I used them to make a bit collection of matches of a table row. With each table row check, the correct bit was set to 1 or 0 in case of a match or a non-match.
But this rowcheck function was very slow. More then 3 minutes for a 25x25 table for example.

First, I didn't bother about the bad performance, we did a lot within a check: Negative checks, regular expressions and other exotic stuff to support the testers. But the rowcheck function got used more and more and the long idle times became annoying.
We created a lookup table for the powers of 2, optimized the loops and conditional statements, but it still underperformed.

Then, we measured the time for a "x = and(a, b);" call and it was 1 tenth of a second. This means more then one minute in case of 25x25 checks, and we not only used it onced, but three times in one iteration.
I assumed that the and() function had to be fast, because it was fast in C, the language the external lib was written in.
After changing the bit collection through and()s and or()s to an array and performing calculations on the array (the product as an and(), the sum for an or()) the performance was increased to 23 seconds for a 25x25 table check. Even with regular expression and negative testing in place.

5. Function calls can be slow
Whenever a function is called, a function is created on the stack, initialized, executed and destroyed. Processors are fast nowadays, but it still takes some time.
I used to use isEmpty({variable}) and isNotEmpty({variable}) for a check on empty or not. The only thing the function did was:

public function isEmpty(inValue) {
    return (inValue == "");
}

It seemed smart on that moment, because if we created other definitions for empty, we just have to enter it in the function to implement it everywhere in the system. Unfortunately, we never came up with other definitions. We noticed, the isEmpty(myVariable) function was seven times as slow then a normal myVariable == EMPTY statement (EMPTY equals "" in our system).
With this knowledge we replaced all isEmpty() functions in time critical functionality, such as the table row search function mentioned above.

A little side note:
    myVariable == EMPTY
or
    myVariable == ""
does not make any difference in WinRunner.

Last word
As mentioned before, this is how you optimize code on a very low level. As long as your design is not right, use senseless synchronization timers or bad synchronization mechanisms, the performance of your test will not increase significantly. Optimization must be in balance on all levels of the test process; From scripting automated tests to requirements and risks.