6
Variables

Video There is a brief overview video available covering the most important aspects of variable handling in QF-Test.

Variables are the primary means to add flexibility to a test suite. Though they are used mainly as parameters for Procedures, they are also useful in many other cases.

Variables can be used in all attributes with text input fields. Many checkboxes can be converted to text input through the button $ at the top left. Then you can insert a boolean value either directly or via a variable.

6.1
Variable references

There are multiple ways to reference variables:

6.1.1
Referencing simple variables

$(variable name) returns the string value of a variable.

If the value is not a string but is evaluated as part of a string or the result is used as text, the text representation of the value is used instead.

6.1.2
Referencing group variables

${group:name} accesses a variable in a variable group. Use it to access variables in a group which contains data from an external source (see External data). Some groups, like qftest, env and system, are always defined and have special meanings (see Special groups).

Again, depending on context, the text representation of the variable value may be returned, regardless of its type.

6.1.3
Referencing variables in scripts and script expressions

Accessing QF-Test variables in scripts and Script expressions (for example $[Jython expression] or the Condition of an If node) is described in Variables.

The run context methods rc.get*

There are multiple methods in the run context module for accessing variables like rc.get*, such as rc.getStr, rc.getInt, rc.getNum, rc.getBool or rc.getObj. A detailed description of all these methods is given in Run context API.

The run context property rc.vars

The run context includes the Map-like object rc.vars for easy access to the current values of QF-Test variables. Designed as an alternative to rc.getObj('name'), it lets you write rc.vars.name instead. When you use this expression to assign a value, it has the same effect of setting a local variable as rc.setLocal.

The run context property rc.groups

Similar to rc.vars, you can use rc.groups to access group variables: Instead of rc.getObj('group','name') or rc.getObj('qftest','dir.version') you can use rc.groups.group.name or rc.groups.qftest.dir.version. When you use this expression to assign a value, it has the same effect of setting a value in a group as rc.setGroupObject. Note that elements in special groups may not allow write access. In that case, a ReadOnlyPropertyException is thrown.

$(variable name) and ${group:name} in Jython scripts

In Jython scripts and script expressions, QF-Test variables can technically be referenced with the same syntax as in normal nodes. Since the Jython script is a string, the text value of the variable is directly embedded into the Jython code during expansion.

This way of referencing is not recommended. If the variable value contains items like backslashes (\) or line breaks, it can lead to unintended results.

6.2
Variable lookup

To understand the reasons of why and how variables are defined in multiple places, you first have to learn about how the values of variables are determined.

Each variable definition is placed on one of two stacks of so-called bindings. One stack is used for direct definitions and one for fallback bindings or default values. When the value of a variable is requested, for example via $(...), QF-Test first searches the stack of direct bindings from top to bottom, then the stack of fallbacks, also top-down. The first value found is used. If there is no binding at all for a name, an UnboundVariableException is thrown unless you use the special syntax ${default:varname:defaultvalue} to provide a default value for this case as described in Special groups.

Topmost bindings
(highest precedence)
...
Bottommost bindings
(lowest precedence)
Topmost bindings
(highest precedence)
...
Bottommost bindings
(lowest precedence)
Primary stack
(Direct bindings)
Secondary stack
(Default values)
Figure 6.1:  Direct and fallback bindings

The mechanism supports recursive or self-referencing variable definitions. For example, setting a variable named classpath to the value some/path/archive.jar:$(classpath) will extend a binding for classpath with lower precedence. If no such binding exists, a RecursiveVariableException is thrown.

6.3
Defining variables

Variables can be defined in various places.

Variable definition tables

Two-column Tables are used, for example, in a Procedure call to define the parameter names and values to be passed, or in a Procedure node to set default values. In each row, one variable with a name and a value can be defined. In many other nodes, like Test suite, Test set and Test case, variables can be defined in tables, as well.

Procedure return values

A Procedure can return a value. It will be assigned to the variable with the name given in the Procedure call node Variable for return value attribute. The called procedure can control the type of the returned object via the attribute Explicit object type of the Return node.

Check results

One possible result handling in a check node is to assign the result to a variable named in the attribute Variable for result, for example in a Boolean check node.

Return values of capture nodes

Capture nodes like Fetch text assign the received value to a variable named in the attribute Variable name.

Set variable nodes

Variables can also be defined through Set variable nodes. The attribute Explicit object type sets the type of the returned object.

Script nodes

Variables can be set in scripts via the methods rc.setLocal, rc.setGlobal, rc.setLocalJson etc. These can then be used in QF-Test nodes. setGroupObject can be used to set variables in a variable group. For more information, see Scripting and Run context API.

Option dialog

Variables can be set and changed in the options dialog section "Variables" (see Variables). This is especially useful for global, system and command line variables.

System variables
Figure 6.2:  Definition of system variables in the options dialog

6.4
Variable levels

Variables can be declared on different levels. There is the foundational difference in evaluation order of variable definitions between the primary and secondary stack. Both stacks then each have more fixed levels.

6.4.1
Primary stack

For the primary stack, the following order applies:

6.4.1.1
Local test case variables

Local test case variables are located in the upper part of the primary stack. When a node is entered during test execution, the defined variables (but not the fallback values!) are placed on top of the stack and removed again when leaving the stack.

For each node that has a variable definition table, a separate level is created on the primary stack, and the variables defined in the table placed there. Variables from local (not global!) assignments are added or updated in one of the existing levels, for example the return value of a procedure, the result variable of a check or capture node or variables created via Set variable or script nodes. The variable is added/updated in the level of the topmost procedure node, or if not available, of the test case node – if the variable does not already exist in a higher level (e.g. sequence, test step, loop, if node). In that case, the value of the variable is updated in that higher level.

To define local variables, the attribute Local variable must be enabled in the respective node. This can be preconfigured in the QF-Test options (see Variables). In scripts, local variables are set with the methods rc.setLocal, rc.setLocalJson or rc.vars.name = value (see Run context API).

6.4.1.2
Global variables

If the attribute Local variable is not active in nodes that can define variables, these variables are created on the level of global variables. In scripts, global variables are set with the methods rc.setGlobal or rc.setGlobalJson (see Run context API).

Note A global variable can also be created despite Local variable being set if no context is available for local variables. This is the case, for example, if a node is executed directly from the 'Extras' node.

A global variable remains unchanged until it is explicitly updated or cleared or QF-Test is quit. That means that global variables "survive" individual test runs. They serve to exchange values between independent test cases or procedures. Keep in mind that global variables must be defined by running the test before they can be referenced.

If you want to modify global variables without running a test, you can do this either through the debug mode (see Displaying variables in debug mode – Example) or the option dialog section "Variables".

To clear any global variables before a test run, use the menu entry »Run«-»Clear global variables«. If QF-Test is running in batch mode (see Starting QF-Test) global variables are cleared before running any test passed through the command line argument -test <n>|<ID>.

6.4.1.3
Command line argument variables

Command line argument variables can be set when launching QF-Test. These are ranked above variables defined in the Test suite node. On the command line the variables are set via the argument -variable <name>=<value>, see Command line arguments and exit codes.

6.4.1.4
Test suite node variables

Variables on this level of the stack are defined in the Test suite node of the current test suite. Typically, these variables are valid for all tests of the suite and can be overridden via the command line during a batch run if needed. A typical example is the choice of browser for running a web application that should differ between interactive test development and batch execution.

6.4.2
Seconday stack

The following order applies to the secondary stack:

6.4.2.1
Fallback values

When entering a node for which fallback values are defined, these are placed on top of the secondary stack. When a node from another test suite is called, the variables of the Test suite node of the original test suite are removed from the primary stack and placed on top of the secondary stack. When leaving the node (respectively the test suite), the variables are again removed from the secondary stack and, in the case of a test suite, moved to the primary stack into the respective level.

Entries are only placed on the secondary stack if fallback values were definied for a node or the originating test suite has variable definitions in its Test suite node.

6.4.2.2
System-specific variables

Here, path names and JDK- or OS-specific values or similar can be defined. This set of definitions is always located at the bottom of the secondary stack and therefore has the lowest binding priority.

System-specific variables are set in the option dialog section "Variables". They are stored in the system configuration file together with other system options.

6.5
Displaying variables in debug mode – Example

Consider the following example:

Variable example
Figure 6.3:  Variable example

The Sequence "Login" contains a Procedure call of the Procedure "login" which expects two parameters: user and password. The Parameter default values of the Procedure are user=username and password=pwd. The Procedure call overrides these with user=myname and password=mypassword.

The "login" Procedure itself contains Procedure calls of other Procedures. Here, no parameters are passed. The procedures "setUser" and "setPassword" have one entry each in Parameter default values.

The following figure shows the overview of variable definitions when executing the procedure"setUser".

Variable debug stack
Figure 6.4:  Variable definitions

Let's take a closer look at the individual rows of the table:

  1. Procedure setUser: No variables defined.
  2. Procedure call setUser: No variables are passed because it is not required (different from e.g. Java). When checking the variable definitions, QF-Test goes through the table from top to bottom - regardless of procedure or test case borders. As soon as a variable with the matching name is found, the corresponding value is used.
  3. Procedure login: No variables defined.
  4. Procedure call login: Two variables are defined in this procedure call. The row was selected, so you can see the defined variables and their values on the right. At the current execution point of the test, the variable "name" will be used next. Since the first ocurrence of a variable with that name is in this row, the corresponding value "myName" will be used.
  5. Sequence Login: No variables defined.
  6. Test case Test: No variables defined.
  7. Global variables: The variable "client" was defined in the Dependency node, because it is needed in all test cases that interact with the application under test. Global variables remain unchanged until they are explicitly updated or cleared.
  8. Command line: Three variables were defined on the command line. One of them is the name of the browser that should be used for the current test run.
  9. Test suite: The name of the browser stored here would be used as a fallback if no other browser was defined in the rows above.
  10. Secondary stack: Signals the end of the primary stack and the beginning of the secondary stack below.
  11. Procedure setUser: A default value for the variable "name" is stored here. It would be used if no variable of that name existed in the rows above.
  12. Procedure login: Here default values for "name" and "password" are stored, as well. They would be used if no variables with those names existed in the rows above.
  13. System: No variables defined.

6.6
Data types of variables

With a few exceptions, all attribute fields in QF-Test nodes interpret entered values as plain text. Those exceptions are the conditions of If, Test case and Test set nodes, as well as script code attributes which expect valid expressions of a specific syntax.

Since the attributes are usually interpreted as text, a special syntax is needed to access variables or for calculations and string manipulations (see Variable references and Script expressions).

In script nodes, all data types that are available in their scripting language can be used independently of QF-Test. Inside the script interpreters, the data objects of any script can be used, see Variables. However, these will not show up in the variable stack of QF-Test and are not visible in the debug-mode variable definitions table or logged in the run log.

You can use the run context methods rc.setLocal and rc.setGlobal to put a variable from a script onto the QF-Test variable stack. This way, QF-Test variables can be assigned strings, but also values with other data types. To set non-string values in a Set variable node you can use Script expressions in the attribute Default value, or you can enter the text representation of the value there and set the desired object type in Explicit object type.

To access these variables, various methods are available. For QF-Test nodes, these are described in Variable references. For scripts and script expressions, methods are described in Variables, and special ones for Jython scripts in Jython Variables.

A detailed description of all run context methods can be found in Run context API.

6.6.1
JSON data

Data is often provided as JSON objects when working with HTTP requests or WebAPI. If you want to serialize such an object, which means to convert it into a JSON string and store it in a QF-Test variable, you can use the methods rc.setLocalJson() and rc.setGlobalJson() of the run context (see Run context API) in a script node.

If you want to convert a JSON string into a JSON object, you can use rc.getJson() in a script node (see Run context API).

JSON objects can be modified and handled with the methods described in The JSON module.

6.7
External data

You can access external data via Load properties, Excel data file, Database, CSV data file and Load resources nodes. These assign a set of definitions to a group name. You can access the value of a resource or property via the description name with the syntax ${group:name}.

You can also access external data in a Data driver via Excel data file, Database and CSV data file. In that case however, no group is created. Instead, a loop iteration is generated for each row of data, in which the values of the data set are bound to QF-Test variables named according to the data column titles. They can be accessed via the syntax $(column title).

When run in batch mode (see Starting QF-Test) QF-Test clears the resources and properties before the execution of each test given with the -test <n>|<ID> command line argument. In interactive mode, QF-Test keeps them around to ease building a suite, but for a true trial run you should clear them via the »Run«-»Clear resources and properties« menu first.

6.8
Special groups

The following variable groups are always available. Their values can be accessed via the syntax $(group name:variable name), or rc.groups.group.variable in scripts.

system
The group system gives access to the system properties of the Java VM (for programmers: java.lang.System.getProperties()), e.g. ${system:user.home} for the user's home directory or ${system:java.class.path} for the class path with which QF-Test was started. Which names are defined in the group system depends on the utilised JDK.
The group always refers to the VM QF-Test was started with, because variable expansion takes place there.
env
On operating systems which support environment variables like PATH, TMP or JAVA_HOME (practically all systems QF-Test runs on), these environment variables can be accessed with the help of the group env.
decrypt
9.0+

Via the decrypt group you can temporarily decrypt a string for the further usage in QF-Test, e.g. for text field inputs, API tokens or database passwords. In the run log, QF-Test will replace the expanded value by the placeholder ***. A value in a Set variable step can be encrypted by right-clicking and selecting »Encrypt text« from the popup menu.

Note For specific values in QF-Test steps the run log always contains the final value. Please inspect the final run log before sharing it. Also pay attention to the remarks for the Salt for crypting passwords option.

default
3.4+ You can specify a default value for a variable with the group default. The syntax is ${default:varname:defaultvalue}. This is extremely useful for things like generic components or in almost every place where there is a reasonable default for a variable because the default value is then tightly coupled with the use of the variable and doesn't have to be specified at Sequence or test suite level. Of course you should only use this syntax if the variable lookup in question is more or less unique. If you are using the same variable with the same default in different places it is preferable to use normal syntax and explicitly set the default, so that the default for all values can be changed in a single place.
as
9.0+ Like in a Set variable oder Return step it is possible to change the typ of an object using the as group. The syntax is ${as:type:value}, whereas it is possible to reference values from variables in value using $(...). Valid values for type are: string, str, boolean, number, object, pattern, int, integer, long, float, double, cmdline, and json.
id
3.1+ The group id can be used to reference QF-Test component IDs. Values in this group simply expand to themselves, i.e. "${id:whatever}" expands to "whatever". Though QF-Test component IDs can be referenced without the help of this group, its use increases the readability of tests. Most notably however, QF-Test component ID references in this group will be updated automatically in case the referenced target component gets moved or its QF-Test ID changed.
idlocal
4.2.3+ The group idlocal is similar to the id group but includes the path to the current test suite, i.e. "${idlocal:x}" expands to "path/to/current/suite/suite.qft#x". This enforces use of the component referenced in the suite that is current at the time of expansion, irrespective of whether there is a component with the same %attId; in the target suite of a procedure call.
quoteitem
4.0+ Via the quoteitem group you can conveniently escape special characters like '@', '&' and '%' in the name of a textual sub-item index to prevent it from being treated as several items, e.g. "${quoteitem:user@host.org}" will result in "user\@host.org".
quoteregex, quoteregexp
4.0+ The group quoteregex with its alias quoteregexp can be used to escape characters with special meaning in Regular expressions. This is often useful when building regular expressions dynamically or when referencing subitems with special characters in their name by a regular expression index, e.g. "componentid%${quoteregex:foo(baa)}.*" allows you to address the first occurrence of items beginning with 'foo(baa)'.
quotesmartid
6.0.1+ The quotesmartid group is similar to quoteitem. In addition to the item syntax special characters '@', '&' and '%' it also escapes the characters ':', '=', '<' and '>' that have special meaning in SmartIDs, e.g. "${quotesmartid:Name: A & B}" will result in "Name\: A \& B".
qftest
The special group named qftest provides miscellaneous values that may be useful during a test run. The following tables list the values currently defined.
Name Meaning
32 or 32bit No longer relevant because support for 32bit Java for QF-Test was dropped in version 8.0
true if QF-Test is running in a 32bit Java VM - which is not the same as running on a 32bit Operating System - false otherwise.
64 or 64bit No longer relevant because support for 32bit Java for QF-Test was dropped in version 8.0
true if QF-Test is running in a 64bit Java VM, false otherwise.
batch true if QF-Test is running in batch mode, false for interactive mode.
client.baseEngineName.<name> The base name of the primary engine of the client started with the Client attribute set to <name>, e.g. fx.
client.browser.<name> The name/type of the browser of the client started with the Client attribute set to <name>, e.g. safari. Only available for Web clients.
client.connectionMode.<name> The name of the connection mode of the client started with the Client attribute set to <name>. Possible values are qfdriver, cdpdriver, webdriver, and embedded. Only available for Web clients.
client.engine.<name> The primary engine of the client started with the Client attribute set to <name>. The result consists of the base name of the engine and a numerical index, e.g. fx0.
client.engineNames.<name> A list of all connected engines of the client started with the Client attribute set to <name>, e.g.[fx0, web_fx0].
client.exitCode.<name> The exit-code of the last process started with the Client attribute set to <name>. In case the process is still alive the result is the empty string.
client.deviceName.<name> A name for the (emulated) device started with the Client attribute set to <name>. Only available for Android clients after instrumentation, for emulated devices equal to the AVD name.
client.deviceType.<name> The type of the (emulated) device started with the Client attribute set to <name>. Can be emulator for an emulation and device for a connected real device. Only available for Android clients after instrumentation.
client.mainVersion.<name> The main version of the browser or device operating system of the client started with the Client attribute set to <name>, e.g. 121. Only available for Web clients after first browser open and for Android clients after instrumentation.
client.output.<name> The output of the last process started with the Client attribute set to <name>. The maximum size for buffered output is defined by the option Maximum size of client terminal (kB).
client.SDKVersion.<name> The SDK version of the device operating system of the client started with the Client attribute set to <name>, e.g. 121. Only available for Android clients after instrumentation.
client.stdOut.<name> The output on the standard output stream (stdout) of the last process (started with the Client attribute set to <name>). The maximum size for buffered output is defined by the option Maximum size of client terminal (kB).
client.stdErr.<name> The output on the standard error stream (stderr) of the last process (started with the Client attribute set to <name>). The maximum size for buffered output is defined by the option Maximum size of client terminal (kB).
client.version.<name> The version of the browser or device operating system of the client started with the Client attribute set to <name>, e.g. 121.10.2967.10. Only available for Web clients after first browser open and for Android clients after instrumentation.
clients A list of the names of all active process clients, separated by a newline.
clients.all A list of the names of all process clients, separated by a newline. This includes live clients as well as the recent dead clients similar to those listed in the "Clients" menu.
count.exceptions Number of exceptions in the current test run.
count.errors Number of errors in the current test run.
count.warnings Number of warnings in the current test run.
count.testCases Total number of total test cases (run and skipped) in the current test run.
count.testCases.exception Number of test cases with exceptions in the current test run.
count.testCases.error Number of test cases with errors in the current test run.
count.testCases.expectedToFail Number of test cases expected to fail in the current test run.
count.testCases.ok Number of successful test cases in the current test run.
count.testCases.ok.percentage Percentage of successful test cases in the current test run.
count.testCases.skipped Number of skipped test cases in the current test run.
count.testCases.notImplemented Number of not implemented test cases in the current test run.
count.testCases.run Number of run test cases in the current test run.
count.testSets.skipped Number of skipped test sets in the current test run.
dir.cache Cache directory of QF-Test
dir.groovy Directory of Groovy
dir.javascript Directory of JavaScript
dir.jython Directory of Jython
dir.log Log directory of QF-Test
dir.plugin Plugin directory of QF-Test
dir.root Root directory of QF-Test
dir.runlog Run log directory of QF-Test
dir.system System-specific configuration directory of QF-Test.
dir.user User-specific configuration directory of QF-Test
dir.version Version-specific directory of QF-Test
engine.<componentId> Retrieves the GUI engine responsible for the given component (see GUI engines).
language The language in which QF-Test displays its graphical user interface.
license The path to the license file
systemCfg The path to the system configuration file
userCfg The path to the user specific configuration file
executable The qftest executable matching the currently running QF-Test version, including the full path to its bin directory and with .exe appended on Windows. Useful if you need to run QF-Test from QF-Test for example to call a daemon or create reports.
isInRerun "true", if current execution is in rerun mode, "false" otherwise, see Rerunning failing nodes immediately.
isInRerunFromLog "true", if test run has been re-started from run log, "false" otherwise, see Triggering rerun from a run log.
java Standard Java program (javaw under Windows, java under Linux) or the explicit Java argument if QF-Test is started with -java <executable> (deprecated)
java.mainVersion The major version of the JRE that QF-Test currently runs on, using 8 for Java 1.8 so the result is something like 8, 11 or 17.
java.subVersion The sub-version of the JRE that QF-Test currently runs on. For Java 8 the sub-version taken from after the '_', so for java.version 1.8.0_302 this results in 302. For Java 9 or higher this is the minor version, e.g. 9 in case of java.version 11.0.9.
linux "true" under Linux, "false" otherwise
macOS "true" under macOS, "false" otherwise
os.fullVersion The whole version of the operating system
os.mainVersion The main version of the operating system, e.g. "10" for Windows 10
os.name The name of the operating system
os.version The version of the operating system. In some cases that's not the whole one then you should use os.fullversion instead.
project.dir The directory to the current project. This variable is not defined in case the current test suite is not part of a project.
rerunCounter Number of current rerun attempt, default is 0, for details see Rerunning failing nodes immediately.
return The most recent value returned from a Procedure through a Return node.
runID The runid of the current test run. See Reports for further information about the runid.
screen.height Screen height in pixels
screen.width Screen width in pixels
skipNode This magic value is not for the casual user. It causes QF-Test to skip execution of the current node. Its primary use is as the value for a variable defined in the Text attribute of a Text input node which also has its Clear target component first attribute set. An empty value would clear the field whereas $_{qftest:skipnode} leaves the field unchanged. But skipnode is also applicable for fine-grained execution control by placing a variable in the comment of a node and selectively passing $_{qftest:skipnode} to that variable. Please note that you almost always want to use lazy syntax '$_' with this variable. Otherwise its expansion as the parameter in a Procedure call node would cause skipping the whole call.
suite.dir Directory of the current suite
suite.file File name of the current suite without directory
suite.path File name of the current suite including directory
suite.name Get the name of the current test suite.
testCase.name The name of the current Test case, empty if no Test case is currently being executed.
testCase.id The QF-Test ID of the current Test case, empty if no Test case is currently being executed.
testCase.qName The qualified name of the current Test case, including the names of its parent Test sets. Empty if no Test case is currently being executed.
testCase.reportName The expanded report name of the current Test case, empty if no Test case is currently being executed.
testCase.splitLogName The qualified name of the current Test case converted to a filename, including the names of its parent Test sets as directories. Empty if no Test case is currently being executed.
testSet.name The name of the current Test set, empty if no Test set is currently being executed.
testSet.id The QF-Test ID of the current Test set, empty if no Test set is currently being executed.
testSet.qName The qualified name of the current Test set, including the names of its parent Test sets. Empty if no Test set is currently being executed.
testSet.reportName The expanded report name of the current Test set, empty if no Test set is currently being executed.
testSet.splitLogName The qualified name of the current Test set converted to a filename, including the names of its parent Test sets as directories. Empty if no Test set is currently being executed.
testStep.name The name of the current Test step, empty if no Test step is currently being executed.
testStep.qName The qualified name of the current Test step, including the names of its parent Test steps, but not including Test cases or Test sets. Empty if no Test step is currently being executed.
testStep.reportName The expanded report name of the current Test step, empty if no Test step is currently being executed.
thread The index of the current thread. Always 0 except if QF-Test is started with the argument -threads <number>.
threads The number of parallel threads. Always 1 except if QF-Test is started with the argument -threads <number>.
version QF-Test version
version.build QF-Test build number
windows "true" under Windows, "false" otherwise
Table 6.1:   Definitions in the special group qftest

3.0+6.9
Immediate and lazy binding

There is a very subtle issue in using QF-Test variables that requires further explanation:

When a new set of variable bindings is pushed on one of the variable stacks, there are two possibilities for handling variable references in the value of a binding, for example when the variable named 'x' is bound to the value '$(y)'. The value '$(y)' can be stored literally, in which case it will be expanded some time in the future when '$(x)' is referenced somewhere, or it can be expanded immediately, so that the value of the variable 'y' is bound instead. The first approach is called 'lazy' or 'late binding', the second approach 'immediate binding'.

The difference, of course, is the time and thus the context in which a variable is expanded. In most cases there is no difference at all, but there are situations where it is essential to use either lazy or immediate binding. Consider the following two examples:

A utility test suite contains a procedure for starting the SUT with different JDK versions. The variable 'jdk' is passed as a parameter to this procedure. For ease of use, the author of the test suite defines some additional useful variables at test suite level, for example a variable for the java executable named 'javabin' with the value '/opt/java/$(jdk)/bin/java'. At the time 'javabin' is bound in the test suite variables, 'jdk' may be undefined, so immediate binding would cause an exception. But even if 'jdk' were bound to some value, immediate binding would not have the desired effect, because the java executable is supposed to be the one from the JDK defined later by passing the parameter 'jdk' to a procedure. Thus lazy binding is the method of choice here.

Imagine another utility test suite with a procedure to copy a file. Two parameters called 'source' and 'dest' specify the source file and destination directory. The caller of the procedure wants to copy a file called 'data.csv' from the same directory as the calling test suite to some other place. The natural idea is to bind the variable 'source' to the value '${qftest:suite.dir}/data.csv' in the procedure call. With immediate binding, '${qftest:suite.dir}' will indeed expand to the directory in which the calling suite resides. However, if lazy binding were used, the actual expansion would take place inside the procedure. In that case, '${qftest:suite.dir}' would expand to the directory of the utility suite, which most likely is not what the caller intended.

In versions of QF-Test up to and including 2.2 all variable expansion was lazy. As the examples above show, both variants are sometimes necessary. Since immediate binding is more intuitive it is now the default. This can be changed with the option When binding variables, expand values immediately. The option Fall back to lazy binding if immediate binding fails complements this and helps to ease migration of old test suites to the use of Immediate Binding. The warnings issued in this context help locating the few spots where you should use explicit lazy binding as described below. Except for very rare cases where lazy binding is required but immediate binding also works so that the fallback is not triggered, all tests should work out of the box.

In the few cases where it makes a difference whether a variable is expanded immediately or lazily, the expansion of choice can be selected individually, independent of the setting of the above option, by using an alternative variable syntax. For immediate binding use '$!' instead of just '$'. Lazy binding is selected with '$_'. For example, to define a variable at test suite level that specifies a file located in this test suite's directory, use '$!{qftest:suite.dir}/somefile'. If immediate binding is the default and you require lazy binding as in the 'jdk' example above, use '$_(jdk)'.

Note With lazy binding the order of variable or parameter definitions in a node or a data driver did not matter because nothing was expanded during the binding stage. With immediate bindings, variables are expanded top-to-bottom or, in a data driver, left-to-right. This means that if you define x=1 and y=$(x) it will work, with y being set to 1, if x is defined first. If y is defined first the definition will either fail or trigger the lazy definition fallback described above.