8
Variablen

Variablen sind von zentraler Bedeutung, wenn es darum geht, die Wiederverwendbarkeit einer Testsuite zu steigern. Zum Einsatz kommen sie vor allem beim Aufruf von 'Prozeduren'. Variablen sind aber auch in vielen anderen Situationen hilfreich.

Variablen können in fast allen Attributen verwendet werden, mit Ausnahme von Wahrheitswerten (Checkboxen). Es gibt drei Varianten von Variablenreferenzen:

8.1
Ermittlung des Wertes einer Variablen

Um zu erklären, wie und warum Variablen an verschiedenen Stellen definiert werden, müssen wir zunächst darauf eingehen, wie der Wert einer Variablen ermittelt wird, wenn diese bei einem Testlauf referenziert wird.

Ein Satz von Variablendefinitionen, genannt Zuordnungen, wird auf einem von zwei Stapeln abgelegt. Der primäre Stapel ist für tatsächliche bzw. direkte Zuordnungen bestimmt, während der sekundäre Stapel Definitionen von Rückfallwerten aufnimmt. Wenn der Wert einer Variable mittels $(...) angefordert wird, durchsucht QF-Test zunächst den primären Stapel von oben nach unten nach einer passenden Definition. Wird keine solche gefunden, wird die Suche im sekundären Stapel fortgesetzt, ebenfalls von oben nach unten. Bleibt auch diese Suche erfolglos, wird eine UnboundVariableException geworfen, sofern Sie nicht mittels der speziellen Syntax ${default:varname:defaultvalue} einen Defaultwert angegeben haben (vgl. Abschnitt 8.4).

Oberste Definitionen
(höchste Bindungskraft)
...
Unterste Definitionen
(niedrigste Bindungskraft)
Oberste Definitionen
(höchste Bindungskraft)
...
Unterste Definitionen
(niedrigste Bindungskraft)
Primärstapel Sekundärstapel
(direkte Zuordnungen) (Defaultwerte)
Abbildung 8.1:  Direkte Zuordnungen und Defaultwerte

Dieser Mechanismus unterstützt rekursive bzw. selbstbezogene Variablendefinitionen. Zum Beispiel bewirkt das Setzen der Variable classpath auf den Wert irgendein/pfad/archiv.jar:$(classpath) eine Erweiterung des Werts einer Definition von classpath mit geringerer Bindungskraft. Existiert keine solche Definition, wird eine RecursiveVariableException ausgelöst.

8.2
Definition von Variablen

Variablen können an verschiedenen Stellen definiert werden. Die Definition erfolgt über zweispaltige Tabellen (vgl. Abschnitt 2.2.5). Dort kann in jeder Zeile eine Variable mit Name und Wert angegeben werden.

System Variablen
Abbildung 8.2:  System Variablen

Vier Sätze von Variablendefinitionen sind über die globalen Optionen zugänglich:

Systemspezifische Variablen
Hier können Pfadnamen, JDK oder Betriebssystem-spezifische Werte etc. festgelegt werden. Dieser Satz von Definitionen befindet sich immer ganz unten im sekundären Stapel und hat damit die geringste Bindungskraft. Die Variablen werden zusammen mit anderen Systemoptionen in der System-Konfigurationsdatei gespeichert (vgl. Abschnitt 1.4).
Variablen der aktuellen Suite
Variablen, die sich auf die aktuelle Testsuite beziehen, werden mit dieser abgespeichert. Beim Start eines Tests werden die Variablendefinitionen seiner Suite ganz unten im primären Stapel abgelegt. Wird während des Testlaufs eine Prozedur oder eine Komponente in einer anderen Suite referenziert, werden die Variablen dieser Suite vorübergehend oben auf den sekundären Stapel gelegt.
Variablen von der Kommandozeile
Beim Start von QF-Test können Variablen mit dem Argument -variable <Name>=<Wert> definiert werden. Diese Variablen werden oberhalb der Suite-Variablen auf dem primären Stapel abgelegt. Damit haben Variablen von der Kommandozeile stärkere Bindungskraft als die System oder Suite-Variablen. Zum Beispiel kann ein Variable count = 1 für die Testsuite definiert und $(count) als 'Anzahl Wiederholungen' einer 'Schleife' für schnelle Testläufe benutzt werden. Dann können Sie qftest -batch -variable count=100 ... zum tatsächlichen Testen verwenden. Die Kommandozeilenzuordnungen sind hauptsächlich zur Ihrer Information zugänglich, aber man kann sie auch testhalber verändern.
Globale Variablen
Ebenfalls auf dem primären Stapel, direkt oberhalb der Kommandozeilen-Variablen, befinden sich die globalen Variablen. Diese dienen dazu, Werte zwischen voneinander unabhängigen Teilen der Suite auszutauschen. Hier landen zum Beispiel die Werte, die mittels 'Text auslesen' oder 'Index auslesen' Knoten vom SUT ausgelesen werden, oder Definitionen mit Hilfe eines 'Variable setzen' Knotens. Bei der interaktiven Arbeit löscht QF-Test die globalen Variablen nicht, wenn ein Test gestartet wird und macht diese sogar in den Optionen zugänglich. Sie sollten aber im Hinterkopf behalten, dass diese Variablen zunächst durch den Ablauf des Tests definiert werden müssen, bevor sie referenziert werden. Daher sollten Sie vor einem echten Testlauf die globalen Variablen mittels »Wiedergabe«-»Globale Variablen löschen« löschen. Läuft QF-Test im Batchmodus (vgl. Abschnitt 1.5), werden die globalen Variablen vor der Ausführung jedes mit dem Kommandozeilenargument -test <Index>|<Id> angegebenen 'Test' Knotens gelöscht.

Alle weiteren Definitionen sind Teil der Testsuite selbst:

8.3
Variablen Beispiel

Betrachten wir folgendes Beispiel:

Variablen Beispiel
Abbildung 8.3:  Variablen Beispiel

Die 'Sequenz' "Login" enthält einen 'Prozeduraufruf' der 'Prozedur' "login" die zwei Parameter erwartet, user und password. Die Defaultwerte der 'Prozedur' sind user=username und password=pwd. Der 'Prozeduraufruf' überschreibt diese mit user=myname und password=mypassword.

Die "login" 'Prozedur' enthält selbst 'Prozeduraufrufe' von weiteren 'Prozeduren' in einer anderen Testsuite namens "lib.qft", um Name und Passwort in GUI Komponenten des SUT zu schreiben. Nehmen wir an, dass die 'Prozeduren' dieser Bibliothek viele gemeinsame Parameter haben. Anstatt für jede 'Prozedur' die gleichen Defaultwerte festzulegen, werden diese in den Variablen der Testsuite abgelegt. Das ist praktisch bei der Erstellung und Bearbeitung der 'Prozeduren' der Bibliothek, da diese damit einzeln ausgeführt werden können, ohne dass extra 'Prozeduraufrufe' erstellt werden müssen.

Das folgende Diagramm zeigt den Zustand des primären und des sekundären Stapels während der Ausführung der 'Prozedur' "lib.qft#setUser":

'Prozeduraufruf' "lib.qft#setUser"
'Prozeduraufruf' "login"
user=myname
password=mypassword
'Sequenz' "Login"
Globale Variablen
Variablen von der Kommandozeile
Suite Variablen
'Prozedur' "lib.qft#setUser"
Testsuite "lib.qft"
user=libuser
password=libpwd
'Prozedur' "Login"
user=user
password=pwd
System Variablen
Primärstapel Sekundärstapel
(direkte Zuordnungen) (Defaultwerte)
Abbildung 8.4:  Beispiel zu Variablenbindungen

Besonders hervorzuheben ist hier, dass der 'Prozeduraufruf' von "lib.qft#setUser" in der 'Prozedur' "login" den Parameter user nicht noch einmal definieren muss, er wird sozusagen "durchgereicht". Grundsätzlich sollten Sie beim Aufruf einer 'Prozedur' aus einer anderen 'Prozedur' heraus einen Parameter genau dann und nur dann definieren, wenn er noch nicht explizit definiert wurde oder wenn Sie einen anderen Wert verwenden wollen.

8.4
Externe Daten und spezielle Gruppen

Auf externe Daten kann mit Hilfe von 'Ressourcen laden' und 'Properties laden' Knoten zugegriffen werden. Diese weisen einem Satz von Definitionen einen Gruppennamen zu. Den Wert einer Rsesource oder Property mit der Bezeichnung Name erhalten Sie mit der Syntax ${Gruppe:Name}.

Wird ein Test im Batchmodus ausgeführt (vgl. Abschnitt 1.5), löscht QF-Test die Ressourcen und Properties vor der Ausführung jedes mit dem Kommandozeilenargument -test <Index>|<Id> angegebenen 'Test' Knotens. Im interaktiven Modus werden diese aufgehoben, um das Erstellen einer Testsuite zu vereinfachen. Vor einem kompletten Testlauf sollten Sie allerdings mittels »Wiedergabe«-»Ressourcen und Properties löschen« für eine saubere Ausgangsbasis sorgen.

Zusätzlich zu ResourceBundles und Properties sind einige spezielle Gruppen definiert, die immer vorhanden sind:

8.5
Berechnungen

Es kann nötig sein, für den korrekten Ablauf eines Tests kleine Berechnungen durchzuführen. Diese erledigen Sie mit Hilfe der speziellen Variablensyntax $[Ausdruck], die Sie in jedem Attribut, das Variablen unterstützt, verwenden können.

Es werden die Operatoren +, -, *, / und % (Modulo) für ganze und Fließkommazahlen unterstützt. Letztere verwenden den Punkt als Dezimalzeichen, nicht das Komma.

Ausdrücke der Form $[...] bieten aber noch viel weitergehende Möglichkeiten, da sie vom Jython Interpreter ausgewertet werden. Zulässig sind alle Ausdrücke deren Syntax für die Jython Methode eval gültig ist. Näheres zu Jython siehe Kapitel 13.

Hinweis Der Zugriff auf QF-Test Variablen in $[...] Ausdrücken folgt den selben Regeln wie in Jython Skripten (vgl. Abschnitt 13.3.3). Die standard QF-Test Syntax $(...) und ${...:...} kann für numerische und Boolesche Werte verwendet werden. Auf Zeichenketten sollte mittels rc.lookup(...) zugegriffen werden.

3.0+8.6
Immediate und Lazy Binding

Es gibt einen sehr subtilen Aspekt bei der Verwendung von QF-Test Variablen auf den wir noch genauer eingehen müssen:

Wenn ein Satz von Variablendefinitionen auf einen der beiden Stapel gelegt wird gibt es zwei Möglichkeiten zur Behandlung von Referenzen auf Variablen im Wert einer Definition. Hat z.B. die Variable namens 'x' den Wert '$(y)', kann dieser Wert wortwörtlich gespeichert werden, so dass der Wert von '$(y)' erst zu einem späteren Zeitpunkt ermittelt wird, wenn irgendwo '$(x)' referenziert wird. Alternativ kann der Wert von '$(y)' schon beim Binden der Variablen 'x' ermittelt und als Wert von x abgelegt werden. Der erste Ansatz wird als "Lazy Binding" oder "Late Binding" bezeichnet, der zweite als "Immediate Binding".

Der Unterschied zwischen beiden ist natürlich der Zeitpunkt der Expansion und damit der Kontext in dem eine Variable expandiert wird. In den allermeisten Fällen ist das Ergebnis das gleiche, aber es gibt Situationen in denen es von großer Bedeutung ist, Lazy oder Immediate Binding zu verwenden. Betrachten wir die folgenden zwei Beispiele:

Eine Testsuite-Bibliothek stellt eine Prozedur zum Start des SUT mit verschiedenen JDK Versionen bereit. Die Variable 'jdk' wird als Parameter an diese Prozedur übergeben. Zur einfacheren Nutzung definiert der Autor der Bibliothek weitere hilfreiche Variablen auf Testsuite-Ebene, z.B. 'javabin' für das ausführbare Java Programm mit dem Wert '/opt/java/$(jdk)/bin/java'. Zu dem Zeitpunkt, zu dem 'javabin' in den Testsuite Variablen gebunden wird, könnte 'jdk' noch undefiniert sein, so dass Immediate Binding zu einem Fehler führen würde. Doch selbst wenn 'jdk' mit einem Standardwert belegt ist hat Immediate Binding nicht den gewünschten Effekt, da sich der Wert von 'javabin' durch Übergabe eines anderen Wertes für 'jdk' an die Prozedur nicht mehr ändert. Lazy Binding ist hier also die Methode der Wahl.

Betrachten wir eine andere Bibliothek mit einer Prozedur zum Kopieren einer Datei. Die zwei Parameter namens 'source' und 'dest' legen die Ausgangsdatei und das Zielverzeichnis fest. Der Aufrufer der Prozedur möchte die Datei 'data.csv' aus dem Verzeichnis seiner Testsuite an einen anderen Ort kopieren. Die nahe liegende Idee ist, für den Parameter 'source' den Wert '${qftest:suite.dir}/data.csv' an die Prozedur zu übergeben. Mit Immediate Binding liefert '${qftest:suite.dir}' in der Tat das Verzeichnis der aufrufenden Suite. Wird allerdings Lazy Binding verwendet, findet die Expansion erst innerhalb der Prozedur statt, so dass '${qftest:suite.dir}' das Verzeichnis der Bibliotheks-Suite liefert, was im Allgemeinen nicht den gewünschten Effekt hat.

In QF-Test Versionen bis einschließlich 2.2 wurde ausschließlich Lazy Binding unterstützt. Wie die obigen Beispiele zeigen, sind beide Varianten sinnvoll und notwendig. Da es intuitiver und leichter nachvollziehbar ist, ist Immediate Binding nur der Standard, was mittels der Option Werte von Variablen beim Binden sofort expandieren geändert werden kann. Die Option Lazy Binding verwenden falls sofortiges Expandieren scheitert ergänzt diese und ermöglicht eine einfache Migration von älteren Testsuiten zum Gebrauch von Immediate Binding. Die Warnungen, die in diesem Zusammenhang ausgegeben werden, helfen Ihnen, die wenigen Stellen zu lokalisieren, an denen Sie wie unten beschrieben explizit Lazy Binding verwenden sollten. Bis auf die äußerst seltenen Fälle, in denen Lazy Binding benötigt wird, Immediate Binding aber auch funktioniert, so dass kein Rückgriff auf Lazy Binding gemacht wird, sollten alle Tests ohne Änderungen lauffähig sein.

In den wenigen Fällen, in denen es einen Unterschied macht, ob eine Variable mittels Immediate oder Lazy Binding definiert wird, kann dies unabhängig von der Voreinstellung durch eine alternative Variablen-Syntax erzwungen werden. Für Immediate Binding verwenden Sie '$!' anstatt nur '$', Lazy Binding erhalten Sie mittels '$_'. Um z.B. auf Testsuite-Ebene eine Variable zu definieren, deren Wert eine Datei im Verzeichnis dieser Suite ist, verwenden Sie '$!{qftest:suite.dir}/somefile'. Wenn Sie wie in obigem 'jdk' Beispiel Lazy Binding benötigen, verwenden Sie '$_(jdk)'.

Hinweis Mit Lazy Binding war es egal, in welcher Reihenfolge Variablen oder Parameter in einem Knoten oder Datentreiber definiert waren, da während des Bindens keine Expansion stattfand. Bei Immediate Binding werden Variablen von oben nach unten, bzw. bei Datentreibern von links nach rechts expandiert. Das bedeutet, dass die Definition von x=1 und y=$(x) funktioniert und y den Wert 1 erhält, wenn x zuerst definiert wird. Kommt hingegen die Definition von y zuerst, führt dies zu einem Fehler oder dem oben beschriebenen Rückfall auf Lazy Binding.