11 Skripting 
                
                    
    
                
                          
      Das Video 'Skripting in QF-Test'
      (Grundlagen) behandelt die Grundlagen des Skriptens.
    
                
Das Video 'Skripting in QF-Test' (Fortgeschritten) zeigt weitere Möglichkeiten des Skriptens.
Es ist einer der großen Vorteile von QF-Test, dass komplexe Tests erstellt werden können, ohne eine einzige Zeile Code zu schreiben. Allerdings gibt es Dinge, die sich mit einer GUI alleine nicht bewerkstelligen lassen. Für ein Programm, das Daten in eine Datenbank schreibt, könnte es z.B. sinnvoll sein, zu überprüfen, ob die Daten korrekt geschrieben wurden. Oder man könnte Testdaten aus einer Datenbank oder einer Datei lesen und mit diesen einen Test ausführen. All das und mehr wird mithilfe der mächtigen Skriptsprachen Jython, Groovy und JavaScript ermöglicht.
4.2+ Jython ist von Anfang an dabei, Groovy seit QF-Test Version 3. Ab Version 4.2 kann man auch JavaScript als Skriptsprache verwenden. Es ist eine Frage des Geschmacks, welcher dieser Sprachen man den Vorzug gibt. Wer jedoch bereits mit Java vertraut ist, wird sich wahrscheinlich eher mit Groovy denn mit Jython anfreunden. Web-Entwickler werden vermutlich JavaScript verwenden.
In diesem Kapitel werden zunächst die Grundlagen der Skriptintegration und die in allen Skriptsprachen zur Verfügung stehenden Module beschrieben. Auf die Besonderheiten der Sprachen Groovy-Skripting, Jython-Skripting und JavaScript-Skripting wird in den jeweiligen Abschnitten eingegangen.
3.0+ Die Skriptsprache eines Knotens wird mit dem Attribut Skriptsprache eines Server-Skript- oder SUT-Skript-Knotens festgelegt. Somit können alle drei Sprachen innerhalb einer Testsuite parallel verwendet werden. Welche Sprache als Standard verwendet werden soll, kann über die Optionen Voreingestellte Sprache für Skript-Knoten und Voreingestellte Sprache für Bedingungen eingestellt werden.
                    11.1 Allgemeines 
                
                      
                Beim Skripting ist die Herangehensweise von QF-Test genau umgekehrt zu der anderer GUI Testprogramme. Anstatt den gesamten Test durch ein Skript zu steuern, bettet QF-Test kleine Skripte in die Testsuite ein. Dies geschieht mithilfe der Knoten Server-Skript und SUT-Skript.
Beiden Knoten gemeinsam ist das Attribut Skript für den eigentlichen Programmcode.
 
                    rc-Methoden
                    3.0+ Der in QF-Test integrierte Skripteditor verfügt über ein paar nützliche Eigenschaften, die das Eingeben des Codes erleichtern. Reservierte Schlüsselwörter, eingebaute Funktionen, Standard-Objekttypen, Literale und Kommentare werden farblich hervorgehoben. Innerhalb von Blöcken werden Codezeilen automatisch eingerückt und am Blockende wieder ausgerückt. Mit Hilfe von TAB können auch mehrere markierte Zeilen von Hand ein- oder ausgerückt (Shift+TAB) werden.
                            Das vielleicht - zumindest für den QF-Test Neuling - wichtigste Feature des integrierten
        Editors ist jedoch die Eingabehilfe für viele eingebaute Methoden. Gibt man
        beispielsweise rc. ein (und ggf. zusätzlich einen oder mehrere
        Anfangsbuchstaben eines Methodennamens) und drückt dann Ctrl+Leertaste, so erscheint ein Popup-Fenster mit den passenden Runcontext
        Methoden und ihrer Beschreibung (vgl. Kapitel 50). Nach Auswahl
        einer Methode und anschließender Bestätigung mit Eingabe wird die
        gewählte Methode in den Skriptcode eingefügt. Drückt man Ctrl+Leertaste nach einem Leerzeichen, wird eine Liste aller Objekte angezeigt, für
        die Hilfe zur Verfügung steht.
      
                
Server-Skripte sind für Dinge wie das Berechnen von Variablenwerten oder das Einlesen und Parsen von Testdaten nützlich. SUT-Skripte öffnen dagegen den unbeschränkten Zugang zu den Komponenten des SUT und zu allen anderen Java-Schnittstellen, die das SUT bietet. Ein SUT-Skript könnte z.B. zum Auslesen oder Überprüfen von Werten im SUT verwendet werden, auf die QF-Test keinen Zugriff hat. Im SUT-Skript-Knoten muss das Attribut Client auf den Namen des SUT Clients gesetzt sein, in dem es ausgeführt werden soll.
Server-Skripte werden in jeder Skriptsprache jeweils in einem Interpreter ausgeführt, der in QF-Test selbst integriert ist, während SUT-Skripte in jeweils einem im SUT integrierten Interpreter laufen. Diese Interpreter sind voneinander unabhängig und haben keine gemeinsamen Zustände. QF-Test nutzt die RMI Verbindung zum SUT für eine nahtlose Integration der SUT-Skripte in die Testausführung.
Über die Menüeinträge »Extras«-»Jython-Konsole«, »Extras«-»Groovy-Konsole« etc. können Sie ein Fenster mit einer interaktiven Kommandozeile für die in QF-Test eingebetteten Interpreter öffnen. Darin können Sie mit der jeweiligen Skriptsprache experimentieren, um ein Gefühl für die Sprache zu entwickeln, aber auch komplexe Dinge ausprobieren wie z.B. das Herstellen der Verbindung zu einer Datenbank. Mittels Strg+Hoch und Strg+Runter können Sie frühere Eingaben wieder verwenden. Außerdem können Sie beliebige Zeilen in der Konsole bearbeiten oder eine Region markieren und mittels Return an den Interpreter schicken. Dabei filtert QF-Test die vom Interpreter stammenden '>>>' und '...' Markierungen heraus.
Entsprechende Konsolen gibt es auch für SUT-Clients. Diese Konsolen sind über das »Clients«-Menü zugänglich.
                            Hinweis Wenn Sie in einer SUT-Skripting-Konsole arbeiten, müssen Sie eines
        beachten: Die Kommandos werden vom Interpreter nicht im Event Dispatch Thread
        ausgeführt, im Gegensatz zu Kommandos, die in einem
        SUT-Skripten-Knoten ausgeführt werden.
        Das sagt Ihnen möglicherweise nichts und meistens stellt es auch kein
        Problem dar, aber wenn Sie auf Swing- oder SWT-Komponenten zugreifen oder deren Methoden
        aufrufen, besteht die Gefahr, dass die gesamte Applikation einfriert. Um das zu
        verhindern stellt QF-Test die globale Funktion runAWT (bzw. runSWT,
        runFX, runWeb, runWin, runAndroid und
        runIOS) zur Verfügung, mit deren Hilfe Sie beliebigen Code im Dispatch
        Thread ausführen können. Um zum Beispiel die Anzahl der sichtbaren Knoten einer
        JTree Komponente namens tree zu ermitteln, verwenden Sie
        runAWT("tree.getRowCount()") (bzw. runAWT { tree.getRowCount() }
        in Groovy) um ganz sicherzugehen.
      
                
                    11.2 Skriptausdrücke 
                
                      
                Manchmal ist es hilfreich, in Knotenattributen kleinere Berechnungen oder Textmanipulationen direkt auszuführen. In QF-Test ist dies an allen Stellen möglich, an denen auch QF-Test Variablen durch ihre Werte ersetzt werden. Hierzu steht eine besondere Syntax zur Verfügung, über die einzeilige Skriptausdrücke ausgewerten werden können:
- 
                                  $[Jython-Ausdruck]wertet den angegebenen Ausdruck im Jython-Interpreter aus. Alternativ kann${jython:Jython-Ausdruck}verwendet werden. Es sind alle Ausdrücke zulässig, deren Syntax für die Jython Methodeevalgültig ist.
- 
                                  Um einen Groovy-Ausdruck zu verarbeiten, verwenden Sie
          ${groovy:Groovy-Ausdruck},
- 
                                  Für einen JavaScript-Ausdruck ${javascript:JavaScript-Ausdruck}.
Die Auswertung der Skriptausdrücke erfolgt nach den gleichen Regeln wie für die Skriptknoten, siehe Jython-Skripting, Groovy-Skripting beziehungsweise JavaScript-Skripting.
                            HinweisAuch der Zugriff auf QF-Test Variablen in ${Skriptsprache:Ausdruck}
        beziehungsweise $[...]-Ausdrücken folgt denselben Regeln wie in den
        Skripten für die entsprechende Sprache. Vor der Ausführung des Codes werden QF-Test Variablen
        im Ausdruck ausgewertet, d.h. die standard QF-Test
        Syntax $(...) und ${...:...} kann für
        numerische und Boolesche Werte verwendet werden. Auf Zeichenketten
        sollte mittels rc.getStr zugegriffen werden
        (vgl. Abschnitt 11.3.3).
      
                
                            Beispiel: In einem Schleife-Knoten
        ist der Ausdruck $[$(IndexLetzteZeile) + 1]
        in Anzahl Wiederholungen hilfreich, wenn zuvor die Variable
        IndexLetzteZeile über den Knoten Index auslesen
        mit dem Index &-1 für die letzte Zeile (zum Beispiel #List:&-1)
        gesetzt wurde.
      
                
                            Skriptausdrücke können auch gekapselte Objekte zurückgeben.
        Beispiel: Die Variable myList erhält den Wert
        $[ ["Affe", "Biber", "Chincilla"] ]. In einem
        Schleife-Knoten kann man nun
        $[len(rc.vars.myList)]
        in Anzahl Wiederholungen angeben.
      
                
In den Bedingungsfeldern von If, Testfall und Testfallsatz ist diese spezielle Syntax nicht erforderlich. Hier können Skriptausdrücke direkt eingegeben werden.
                    11.3 Der Runcontext rc 
                
                      
                Zur Ausführung von Server-Skripten und SUT-Skripten stellt QF-Test eine spezielle Umgebung zur Verfügung, zu der u.a. das Runcontext Objekt gehört, das den aktuellen Zustand der Ausführung eines Tests repräsentiert. Auf dieses Objekt kann über die Variable "rc", welche in allen Sprachen verfügbar ist, zugegriffen werden. Es bietet Schnittstellen (vollständig dokumentiert in Abschnitt 50.5) für den Zugriff auf QF-Test Variablen, zum Aufruf von QF-Test Prozeduren und um Meldungen in das Protokoll zu schreiben. Ein SUT-Skript kann mit seiner Hilfe außerdem auf die echten Java-Komponenten des GUI im SUT zugreifen.
                            Für Fälle, in denen kein Runcontext verfügbar ist, z.B. Resolver, TestRunListener, Code
        der in einem Hintergrund-Thread ausgeführt wird etc. bietet QF-Test ein Modul namens
        qf mit hilfreichen generischen Methoden zum Logging und für andere Zwecke an.
        Detaillierte Informationen hierzu finden Sie in Abschnitt 50.6.
      
                
                    11.3.1 Meldungen ausgeben 
                
                        
                Ein Einsatzgebiet des Runcontexts ist die Ausgabe beliebiger Meldungen im Protokoll, das QF-Test für jeden Testlauf erstellt. Diese Meldungen können auch als Warnungen oder Fehler markiert werden.
rc.logMessage("This is a plain message")
rc.logWarning("This is a warning")
rc.logError("This is an error") 
                    
                              Wird mit kompakten Protokollen gearbeitet (vgl. die Option Kompakte Protokolle erstellen), werden Knoten, die aller
          Wahrscheinlichkeit nach nicht für eine Fehleranalyse benötigt werden, eventuell
          aus dem Protokoll entfernt, um Speicher zu sparen. Dies betrifft nicht die
          Fehlermeldung (rc.logError). Hier wird immer die Meldung selbst und
          etwa 100 vorhergehende Knoten im Protokoll aufgehoben. Bei einer Warnung
          rc.logWarning wird auf jeden Fall die Warnung behalten, jedoch
          keine vorhergehenden Knoten. Normale Meldungen (rc.logMessage) werden
          gegebenenfalls entfernt. Wenn Sie eine normale Meldung zwingend im Protokoll
          behalten wollen, können Sie dies über den optionalen zweiten Parameter
          (dontcompactify) erreichen:
        
                
rc.logMessage("This message will not be removed", dontcompactify=true)
rc.logMessage("This message will not be removed", 1) 
                    
                    11.3.2 Checks durchführen 
                
                        
                
                              Die Ausgabe einer Meldung ist meist an eine Bedingung geknüpft.
          Außerdem ist es oft wünschenswert, im XML- oder HTML-Report ein
          Ergebnis analog zu einem Check-Knoten zu erhalten.
          Hierzu dienen die Methoden rc.check und rc.checkEqual:
        
                
x = 0
rc.check(x == 0, "Value of x is 0")
userlang = rc.getStr("system", "user.language")
rc.checkEqual(userlang, "en", "English locale required",
              rc.EXCEPTION) 
                    
                              Das optionale letzte Argument legt die Fehlerstufe fest.
          Hierbei können rc.EXCEPTION, rc.ERROR, rc.OK bzw. rc.WARNING verwendet werden.
        
                
                    11.3.3 Variablen 
                
                          
                In QF-Test gibt es verschiedene Arten von Variablen. Es wird zunächst unterschieden zwischen QF-Test Variablen, siehe Kapitel 6, und Variablen der Skriptsprachen. Die Variablen der Skriptsprachen wiederum werden unterteilt in Server- und SUT-seitige Variablen des jeweiligen Interpreters. Die folgende Grafik verdeutlicht die Sichtbarkeit der jeweiligen Variablenarten:
 
                    Um in den Skripten mit diesen unterschiedlichen Variablen zu arbeiten und dieses auszutauschen, stellt der Runcontext spezielle Methoden zur Verfügung. Diese Methoden werden in den nachfolgenden Abschnitten erläutert.
                    11.3.3.1 Zugriff auf Variablen 
                
                        
                
                              Auf Variablen von QF-Test in einem Skript zuzugreifen ist nicht weiter
          schwierig. Auf Textvariablen können Sie zum Beispiel mittels der Runcontext-Methode getStr,
          auf boolsche Werte mittels getBool, auf ganze Zahlen mit getInt,
          auf numerische Werte mit getNum und auf Datenobjekte mit getObj zugreifen
          (siehe Abschnitt 50.5 für eine vollständige API-Beschreibung).
        
                
# access a simple variable
text = rc.getStr("someText")
# access a property or resource
version = rc.getStr("qftest", "version") 
                    rc.getStr
                    
                    11.3.3.2 Variablen setzen 
                
                        
                
                              Um die Ergebnisse eines Skripts für die weitere Ausführung
          eines Tests bekannt zu machen, können Werte in globalen oder lokalen
          QF-Test Variablen abgelegt werden. Der Effekt entspricht der Ausführung
          eines Variable setzen-Knotens. Die entsprechenden Methoden im
          Runcontext sind rc.setGlobal und
          rc.setLocal.
        
                
# Test if the file /tmp/somefile exists
from java.io import File
rc.setGlobal("fileExists", File("/tmp/somefile").exists()) 
                    rc.setGlobal
                    
                              Nach Ausführung des obigen Skripts wird
          $(fileExists) in einem Knoten von QF-Test zu True expandieren, wenn die Datei
          /tmp/somefile existiert und zu 'false', wenn sie nicht existiert.
        
                
                              Um eine Variable zu löschen, setzen Sie deren Wert auf None in Jython bzw. null in Groovy und JavaScript. Mittels
          rc.clearGlobals() aus einem Server-Skript können alle globalen
          Variablen gelöscht werden.
        
                
                    11.3.3.3 Globale Skript-Variablen 
                
                        
                
                              Manchmal ist es hilfreich, eine Skript-Variable in verschiedenen Skriptknoten zur
          Verfügung zu haben. Falls der Wert der Variablen kein simpler String oder Integer ist,
          genügt es eventuell nicht, diese mit setGlobal als globale QF-Test Variable zu
          definieren, da der Wert beim Zugriff von/einem SUT-Skripte serialisiert werden muss.
          In solchen Fällen können Sie die Variable zum Beispiel in Jython als global deklarieren,
          um auf sie aus verschiedenen Knoten oder Prüfausdrücken
          derselben Skriptsprache zuzugreifen, wie es das folgende Beispiel zeigt.
        
                
global globalVar globalVar = 10000
                              globalVar steht nun in allen folgenden Jython-Skriptknoten zur
          Verfügung (in allen Jython-Server-Skripte oder
          in allen Jython-SUT-Skripte desselben Clients). Um
          den Wert von globalVar in einem anderen Jython-Skriptknoten zu verändern, ist
          erneut eine Deklaration mit dem Schlüsselwort global notwendig. Andernfalls
          wird eine neue lokale Variable mit gleichem Namen erzeugt. Um eine globale Jython
          Variable zu entfernen, kann die del Anweisung verwendet werden:
        
                
global globalVar del globalVar
In Groovy und JavaScript werden globale Variablen noch einfacher erzeugt als in Jython. Die Regel lautet, dass undeklarierte Variablen im Binding des Skripts erwartet werden. Sind sie dort nicht zu finden, werden sie automatisch hinzugefügt.
myGlobal = 'global'
assert myGlobal == 'global'
def globals = binding.variables
assert globals['myGlobal'] == 'global'
globals.remove('myGlobal')
assert globals.find { it == 'myGlobal' } == null 
                    
                    11.3.3.4 Austausch von Variablen zwischen verschiedenen
          Prozessen 
                
                        
                Es kommt vor, dass Variablen, die in einem Prozess definiert wurden, später in einem anderen Prozess benötigt werden. So könnte zum Beispiel eine Liste von Werten, die mit Hilfe eines SUT-Skripts aus einer Tabelle gelesen werden, in einem Server-Skript weiterverwendet werden, um darüber zu iterieren.
                              Die einfachste Möglichkeit ist nun, die Liste mit Hilfe von rc.setGlobal
          oder rc.setLocal in einer QF-Test Variable zu speichern und
          später den Inhalt mit rc.getObj abzufragen. Dies funktioniert,
          sofern der gespeicherte Inhalt serialisierbar ist, die Objekte im anderen
          Prozess wieder hergestellt werden können, und keine Ausnahme definiert wurde
          (siehe Von der Serialisierung ausgenommene Objekt-Klassen). Andernfalls liefert rc.getObj
          automatisch den String-Wert des Objektes zurück - vergleichbar mit rc.getStr.
        
                
                              Als Alternative stellt der Runcontext einen
          symmetrischen Satz von Methoden zum Zugriff auf und zur Modifikation
          von Skript-Variablen in einem anderen Prozess bereit. Für
          SUT-Skripte sind dies die Methoden toServer
          und fromServer. Die entsprechenden Methoden für
          Server-Skripte heißen toSUT und
          fromSUT.
          Dabei müssen die Skript-Knoten jeweils die gleiche Skriptsprache verwenden.
        
                
Das folgende Jython Beispiel zeigt, wie ein SUT-Skript direkt eine globale Variable im Jython-Interpreter von QF-Test setzen kann:
cellValues = []
table = rc.getStr("idOfTable")
for i in range(table.getRowCount()):
    cellValues.append(table.getValueAt(i, 0))
rc.toServer(tableCells=cellValues) 
                    Nach Ausführung des obigen Skripts enthält die globale Variable namens "tableCells" in QF-Test's Jython-Interpreter das Array der Werte aus der Tabelle.
Hinweis Die Tabellenwerte im obigen Beispiel sind nicht notwendigerweise Strings. Sie könnten Zahlen sein, Datumswerte, was auch immer. Leider ist der pickle Mechanismus von Jython nicht mächtig genug, um Instanzen von Java-Klassen zu transportieren (nicht einmal von serialisierbaren), sodass dieser Austauschmechanismus auf primitive Typen wie Strings und Zahlen sowie auf Jython Objekte und Strukturen wie Arrays und Dictionaries beschränkt ist.
                    11.3.4 Zugriff auf die GUI-Komponenten des SUT 
                
                        
                
                              Für SUT-Skripte bietet der Runcontext eine äußerst
          nützliche Methode. Durch den Aufruf von
          rc.getComponent("componentId") werden die Informationen
          aus dem Komponente-Knoten mit der QF-Test ID
          "componentId" aus der Testsuite geholt und an den Mechanismus zur
          Wiedererkennung von Komponenten gereicht. Dieser arbeitet genau wie
          bei der Simulation eines Events, das heißt, er wirft auch die
          entsprechenden Exceptions, falls die Komponente nicht gefunden
          werden kann.
        
                
Im Erfolgsfall wird die Komponente an das Skript zurückgegeben und zwar nicht in Form von abstrakten Daten, sondern das konkrete Objekt. Alle Methoden, die die Java-API der Klasse dieser Komponente zur Verfügung stellt, können ausgeführt werden, um Informationen auszulesen oder um Effekte zu erzielen, die durch das GUI nicht möglich sind. Um eine Liste der Methoden einer Komponente anzuzeigen, siehe Abschnitt 5.12.
# get the custom password field
field = rc.getComponent("tfPassword")
# read its crypted value
passwd = field.getCryptedText()
rc.setGlobal("passwd", passwd)
# get the table component
table = rc.getComponent("tabAddresses")
# get the number of rows
rows = table.getRowCount()
rc.setGlobal("tableRows", rows) 
                    rc.getComponent
                    
                              Sie können auf diesem Weg auch auf Unterelemente zugreifen. Wenn der
          Parameter componentId ein Element referenziert, liefert
          getComponent ein Paar zurück, bestehend aus der
          Komponente und dem Index des Elements. Der Index kann dazu verwendet
          werden, den eigentlichen Wert zu ermitteln. Das folgende Beispiel
          zeigt, wie Sie den Wert einer Tabellenzelle auslesen. Beachten Sie
          dabei auch die praktische Methode mit der Jython das Auspacken von
          Sequenzen bei Zuweisungen unterstützt.
        
                
# first get the table and index
table, (row,column) = rc.getComponent("tableAddresses@Name@Greg")
# then get the value of the table cell
cell = table.getValueAt(row, column) 
                    rc.getComponent
                    
                    11.3.5 Aufruf von Prozeduren 
                
                        
                
                              Der Runcontext kann auch dazu verwendet werden, Prozeduren
          in QF-Test auszuführen.
        
                    rc.callProcedure("text.clearField",
         {"component" : "nameField", "message" : "nameField cleared"}) 
                        
In obigem Beispiel wird die Prozedur namens "clearField" im Package namens "text" aufgerufen. Die Parameter für den Aufruf sind "component" mit dem Wert "nameField" und "message" mit dem Wert "nameField cleared".
Dasselbe Beispiel mit der veränderten Groovy Syntax:
rc.callProcedure("text.clearField",
         ["component" : "nameField", "message" : "nameField cleared"]) 
                    Und in JavaScript:
rc.callProcedure("text.clearField",
         {"component" : "nameField", "message" : "nameField cleared"}) 
                    
                              Der Rückgabewert einer Prozedur, der mittels eines Return-Knotens
          festgelegt werden kann, ist gleichzeitig der Rückgabewert des
          rc.callProcedure Aufrufs.
        
                
                              Hinweis In einem SUT-Skript-Knoten sollte
          rc.callProcedure nur mit großer Vorsicht verwendet werden. Rufen Sie
          nur Prozeduren mit kurzer Laufzeit auf, die keine allzu komplexen Operationen im
          SUT auslösen. Andernfalls könnte eine DeadlockTimeoutException verursacht
          werden. Wenn Daten für datengetriebene Tests zwingend im SUT ermittelt werden müssen,
          speichern Sie diese mittels rc.setLocal in einer Variable bzw.
          transferieren Sie diese mittels rc.toServer zu QF-Test's Interpreter und
          treiben Sie die Tests dann aus einem Server-Skript-Knoten, für den es keine
          derartigen Einschränkungen gibt.
        
                
                    3.1+11.3.6 Setzen von Optionen 
                
                        
                
                              Viele der in Kapitel 41 beschriebenen Optionen können auch zur Laufzeit via
          rc.setOption gesetzt werden. Konstanten für die Namen dieser Optionen sind in der Klasse
          Options definiert, welche in den Skriptsprachen automatisch verfügbar ist.
        
                
                              Ein reelles Beispiel, bei dem es sinnvoll ist, eine Option temporär zu setzen, ist die
          Wiedergabe eines Events auf eine deaktivierte Komponente. Für diesen Sonderfall muss die
          Überprüfung durch QF-Test auf den enabled/disabled Zustand verhindert werden. Zum Setzen mit umgehenden
	  Zurücksetzen gibt es die Variante pushOption / popOption, bei der vorhergehende
	  setOption Aufrufe nicht verloren gehen:
        
                
rc.pushOption(Options.OPT_PLAY_THROW_DISABLED_EXCEPTION, false)
Nach Abspielen des speziellen Events kann der vorhergehende Wert der Option wiederhergestellt werden, wie in folgendem Beispiel gezeigt:
rc.popOption(Options.OPT_PLAY_THROW_DISABLED_EXCEPTION)
                    	  Möchte man ganz sicher gehen, dass der Wert korrekt zurückgesetzt wird, sollten die beiden Skript-Knoten in
	  einer Try / Finally Kombination verbaut werden. Andernfalls würde z.B. eine
	  ComponentNotFoundException beim Abspielen des Events das Zurücksetzen verhindern.
	
                
Hinweis Achten Sie darauf, dass Sie QF-Test Optionen immer in einem Server-Skript-Knoten und SUT-Optionen in einem SUT-Skript-Knoten setzen, andernfalls hat die Aktion keinen Effekt. Einige Optionen - speziell für SmartIDs - haben Effekte sowohl auf QF-Test, als auch auf SUT Seite. Diese müssen in einem Server-Skript-Knoten geändert werden. QF-Test leitet diese Änderung automatisch an die SUT-Clients weiter. Die Dokumentation der Optionen in Kapitel 41 führt für jede Option die betroffene Seite - Server und/oder SUT - auf.
                    11.3.7 Komponenten bei Bedarf explizit setzen 
                
                        
                
                              Es können Fälle auftreten, in denen Sie eine bestimmte Komponente nicht über einen Komponente-Knoten
          definieren können, sondern auf Skript-Ebene
          suchen müssen, um mit dieser arbeiten zu können, sei es, um aus Performance-Gründen dieselbe Komponente
	  mehrfach zu nutzen oder für Spezialfälle in denen die normale Erkennung zu kompliziert oder ineffektiv ist.
	  Für solche Fälle können Sie die
          Methode rc.overrideElement verwenden, um die gefundene
          Komponente einer QF-Test ID oder SmartID zuzuordnen. Anschließend können Sie mit den
          gewohnten QF-Test Knoten mit dieser ID arbeiten.
        
                
                    	  Hinweis
	  Das folgende Beispiel wäre zwar heute mit SmartID sehr einfach zu lösen, ist aber immer noch illustrativ. Für
	  komplexere Fälle bleibt overrideElement weiterhin relevant.
	
                
                              Stellen Sie sich vor, wir möchten immer mit dem ersten Textfeld
          eines Panels arbeiten. Jedoch könnte das einfache Aufzeichnen der
          Textfelder nicht möglich sein, da sich der Inhalt zu stark
          ändert. Nun können wir ein Skript implementieren, welches das erste
          Textfeld sucht. Dann können wir dieses gefundene Textfeld einer
          Komponente PriorityAwtSwingComponent aus der
          Standardbibliothek qfs.qft zuordnen.
          Nachdem wir das Skript ausgeführt haben, können mit der Angabe der
          QF-Test ID PriorityAwtSwingComponent alle gewohnten
          QF-Test Knoten benutzen um mit dem gefundenen Textfeld zu arbeiten.
        
                
panel = rc.getComponent("myPanel")
for component in panel.getComponents():
    if qf.isInstance(component, "javax.swing.JTextField"):
        rc.overrideElement("PriorityAwtSwingComponent", component)
        break
 
                    Dieses Konzept ist sehr nützlich, wenn Sie einen Algorithmus kennen, um ihre Zielkomponenten für bestimmte Testschritte zu suchen.
                              Sie können solche (veraltete, s.u.) Priority-Komponenten für alle unterstützten
          Engines in der Standardbibliothek qfs.qft finden.
          Ein Beispiel finden Sie auch in Ihrer QF-Test Installation in der
          mitgelieferten Testsuite carconfigSwing_advanced_de.qft im Verzeichnis
          demo/carconfigSwing.
        
                
                    	  7.0+
	  Vor der Einführung von SmartIDs musste die QF-Test ID eines Komponente-Knotens als
	  id Parameter angegeben werden. Bei Verwendung einer SmartID entfällt diese Anforderung. Sie
	  können eine SmartID beliebig wählen, sie muss nur mit # beginnen. Diese Funktion basiert auf einem einfachen
	  String-Vergleich, eventuell gesetzte Scopes gehen auf dieser Ebene nicht ein! Ebenso neu in QF-Test 7.0 ist die
	  Möglichkeit, überladene Elemente via rc.getOverrideElement auszulesen. Im folgenden Beispiel
	  werden beide Möglichkeiten kombiniert.
        
                
if not rc.getOverrideElement("#FirstTextField"):
    panel = rc.getComponent("myPanel")
    for component in panel.getComponents():
	if qf.isInstance(component, "javax.swing.JTextField"):
	    rc.overrideElement("#FirstTextField", component)
	    break
 
                    
                    11.4 Jython-Skripting 
                
                      
                HinweisJython basiert auf Python 2 und nicht Python 3. Wenn also in diesem Handbuch nur von "Python" ohne genauere Angabe die Rede ist, ist immer Python 2 gemeint.
Python ist eine vielseitige, objektorientierte Skriptsprache, die von Guido van Rossum entworfen und in C implementiert wurde. Hilfreiche Informationen zu Python gibt es unter http://www.python.org. Python ist eine standardisierte Sprache und seit vielen Jahren etabliert. Umfassende Dokumentation dazu ist frei verfügbar, daher beschränkt sich dieses Handbuch darauf, die Integration von Jython in QF-Test zu erklären. Die Sprache selbst ist sehr natürlich und intuitiv. Ihre größte Stärke ist die Verständlichkeit und Lesbarkeit von Python-Skripten. Daher sollten Sie keine Probleme haben, die folgenden Beispiele zu verstehen.
Jython (früher JPython genannt) ist eine Implementierung von Version 2 der Programmiersprache Python in Java. Jython hat dieselbe Syntax wie Python und verfügt über beinahe identische Features. Die Objektsysteme von Java und Jython haben vieles gemeinsam und Jython kann nahtlos in Anwendungen wie QF-Test integriert werden. Das macht es zu einem äußerst nützlichen Werkzeug für Java-Skripting. Jython hat seine eigene Homepage unter http://www.jython.org. Dort gibt es unter anderem auch ein ausführliches Tutorial zum Einstieg.
QF-Test verwendet die Jython Version 2.7, die einen Großteil der Standard Python 2 Bibliothek unterstützt.
                            Die Skriptsprache Jython wird in QF-Test nicht nur in Server-Skript und SUT-Skript
        Knoten verwendet, sondern auch in $[...]-Ausdrücken und standardmäßig zur Auswertung von Bedingungen wie im
        Attribut Bedingung von If-Knoten.
      
                
                    11.4.1 Jython-Variablen 
                
                        
                
                              HinweisIn Jython-Skripten werden QF-Test Variablenreferenzen der Form $(var) oder ${Gruppe:Name} vor Ausführung des
          Skripts expandiert. Dies kann zu unerwünschten Effekten führen, insbesondere wenn die Werte dieser Variablen
          Zeilenumbrüche oder Rückstriche (\) enthalten. Es sollten stattdessen die Methoden rc.getStr()
          und rc.getObj() etc. (vgl. Abschnitt 11.3.3.1)
          oder rc.vars und rc.groups (vgl. Abschnitt 6.1.3)
          verwendet werden, die erst während der Ausführung des Skripts ohne Risiko evaluiert werden.
        
                
                    11.4.2 Module 
                
                	
                Module für Jython in QF-Test sind nichts anderes als gewöhnliche Python-Module. Sie können Module in QF-Test importieren und deren Methoden aufrufen, was die Entwicklung komplexer Skripte stark vereinfacht und außerdem die Wartbarkeit Ihrer Tests erhöht, da Module testsuiteübergreifend verfügbar sind.
                              Module, die Sie für mehrere Testsuiten zur Verfügung
          stellen wollen, sollten Sie im jython Verzeichnis unter
          QF-Tests Wurzelverzeichnis ablegen. Module, die speziell für eine
          Testsuite geschrieben sind, können auch direkt im selben Verzeichnis
          wie die Testsuite liegen. Das versionsspezifische Verzeichnis
          qftest-9.0.6/jython/Lib ist für mitgelieferte Module
          reserviert. Jython-Module haben die Endung
          .py.
	
                
Das folgende Beispiel zeigt ein Jython-Modul, das eine Prozedur zur Verfügung stellt, die eine Liste von Zahlen sortiert:
def insertionSort(alist):
    for index in range(1,len(alist)):
        currentvalue = alist[index]
        position = index
        while position>0 and alist[position-1]>currentvalue:
            alist[position]=alist[position-1]
            position = position-1
        alist[position]=currentvalue 
                    pysort.py
                    Das folgende Jython-Skript ruft die im Modul definierte Prozedur auf.
import pysort alist = [54,26,93,17,77,31,44,55,20] pysort.insertionSort(alist) print(alist)
                    11.4.3 Post-mortem Fehleranalyse von Jython-Skripten 
                
                	
                
                              In Python gibt es einen einfachen zeilenorientierten Debugger namens pdb. Zu
          seinen nützlichen Features gehört die Möglichkeit zu analysieren, warum ein Skript mit
          einer Exception fehlgeschlagen ist. In Python können Sie hierzu einfach nach einer
          Exception das pdb Modul importieren und pdb.pm() ausführen.
          Damit gelangen Sie in eine Debugger-Umgebung in der Sie die Werte der Variablen zum
          Zeitpunkt des Fehlers betrachten und auch den Call-Stack hinauf navigieren können um dort
          weitere Variablen zu analysieren. Das Ganze ist vergleichbar mit der Analyse eines
          Core-Dump einer C-Anwendung.
	
                
                              Obwohl Jython den pdb Debugger grundsätzlich unterstützt, funktioniert er aus
          verschiedenen Gründen in QF-Test nicht besonders gut, aber immerhin ist die post-mortem
          Analyse von Skripts über die Jython-Konsolen möglich. Nach einem fehlgeschlagenen Server-Skript-Knoten
          öffnen Sie QF-Test's Jython-Konsole, für ein gescheitertes SUT-Skript die
          Jython-Konsole des entsprechenden SUT, und geben dort einfach debug() ein.
          Dies sollte denselben Effekt wie das oben beschriebene pdb.pm() haben.
          Weitere Informationen zum Python-Debugger entnehmen Sie bitte der
          Python-Dokumentation.
	
                
Eine Schritt-für-Schritt-Anleitung, wie Jython-Scripte in QF-Test mit externen Werkzeugen debugged werden können, finden Sie in unserem Blogartikel Wie kann man Jython Scripts in QF-Test debuggen? .
                    11.4.4 Boolean-Typ 
                
                        
                
                              Jython hat einen echten Boolean-Typ mit den Werten True und
          False. In älteren Versionen dienten die Integer-Werte 0 und 1 als Boolean-Werte.
          Dies kann zu Problemen führen, wenn das Ergebnis eines Aufrufs wie
          file.exists() einer QF-Test Variable zugewiesen wird, z.B. "fileExists", und
          später in einem Bedingung-Attribut in der Form $(fileExists) ==
          1 ausgewertet wird. Derartige Bedingungen sollten grundsätzlich in der einfachen
          Form $(fileExists) bzw. rc.getBool("fileExists") geschrieben werden,
	      die mit allen Jython-Versionen funktioniert.
        
                
                    11.4.5 Jython Strings und Zeichenkodierung 
                
                	
	
                Zusammenfassung und Hinweise
                    	  5.3+
	  Zeichen in Jython Literalen wie "abc" waren auf 8 Bit limitiert, was zu Problemen bei
	  Verwendung von internationalen Zeichen führte.
	
                
QF-Test Version 5.3 ermöglicht die Verwendung von internationalen Zeichen in Jython Skripten und Bedingung Attributen basierend auf der Option Literale (wörtliche Zeichenketten) in Jython sind Unicode (16-Bit wie in Java).
Falls Sie QF-Test erst seit Version 5.3. oder höher verwenden, ist diese Option standardmäßig aktiv.
Ein kleiner Teil von bestehenden Skripten muss beim Umschalten auf Unicode Literale angepasst werden. Daher bleibt die Option zunächst deaktiviert, falls QF-Test eine bestehende ältere Systemkonfiguration antrifft. Es wird wärmstens empfohlen, diese Option zu aktivieren. Der Abschnitt "Problembehandlung" weiter unten erklärt, was im Fall von dadurch auftretenden Problemen zu tun ist.
Wenn Jython Unicode Literale aktiviert sind, sollte für maximale Flexibilität die Option Standard-Zeichenkodierung für Jython auf "utf-8" gesetzt werden.
                    	  Unabhängig von den eingestellten Option sollte vor allen Dingen die Expansion von QF-Test Variablen in
	  Literalen verhindert werden. Ausdrücke der Form "$(somevar)" können zu Syntaxfehlern oder
	  unerwarteten Ergebnissen führen, wenn der Wert der Variable Zeilenumbrüche oder Rückstriche ('\')
	  enthält. Verwenden Sie stattdessen rc.getStr("somevar").
	
                
Hintergründe und Werdegang von Jython in QF-Test
                              Alle Java-Strings sind Sequenzen von 16-Bit Zeichen. Jython kennt hingegen zwei Arten von Strings: 8-Bit
	  Byte-Strings (type <str>) und 16-Bit Unicode-Strings (type <unicode>). Der überwiegende Anteil
	  von Strings in QF-Test Jython Skripten sind entweder Konstante Zeichenketten wie "abc", genannt
	  Literale, oder Java-Strings, die nach Jython konvertiert werden, wie das Ergebnis von
	  rc.getStr("varname"). Die Konvertierung aus Java führt immer zu 16-Bit Unicode-Strings. Für
	  Literale hängt das Ergebnis von der Option Literale (wörtliche Zeichenketten) in Jython sind Unicode (16-Bit wie in Java) ab.
        
                
Wenn Unicode und Byte-Strings verglichen oder zusammengefügt werden, muss Jython eine Form in die andere konvertieren. Die Konvertierung von Unicode zu Byte-Strings heißt Enkodierung, die umgekehrte Richtung Dekodierung. Es gibt viele verschiedene Wege, 16-Bit Strings in 8-Bit Sequenzen zu kodieren und die Regeln dafür heißen Zeichenkodierung. Typische Beispiele hiefür sind "utf-8" oder "latin-1". Die Option Standard-Zeichenkodierung für Jython legt fest, welche Kodierung Jython verwenden soll, wenn keine explizite angegeben ist. Aus Kompatibilitätsgründen war vor QF-Test 5.3 der Standardwert "latin-1". Inzwischen ist er "utf-8", weil diese Kodierung flexibler ist und alle internationalen Zeichensätze unterstützt.
                    	  Jython in QF-Test basiert auf Python Version 2. In früheren Python Versionen bestanden Strings stets aus 8-Bit
	  Zeichen. Später kamen Unicode-Strings mit 16-Bit Zeichen hinzu. In Python 2 sind Literale wie
	  "abc" 8-Bit Byte-Strings, das Voransetzen von 'u', also u"abc" macht daraus
	  Unicode-Strings. In Python 3 sind Literale bereits Unicode-Strings und können durch Voransetzen von 'b',
	  also b"abc" zu Byte-Strings gemacht werden.
        
                
                              In Jython 2.2 wurden Java-Strings in 8-Bit Python-Strings konvertiert, basierend auf der
          Standard-Zeichenkodierung der Java-VM, in der westlichen Hemisphäre üblicherweise ISO-8859-1 (auch als
          latin-1 bekannt). Seit Jython 2.5 werden Java Strings grundsätzlich als Unicode Jython Strings
          interpretiert. Zusammen mit 8-Bit String-Literalen führt dies zu viel implizierter Konvertierung zwischen
          Byte-Strings und Unicode-Strings, z.B. wenn ein - nun als Unicode interpretierter - Java-String und ein
          Literal verknüpft werden, wie in rc.getStr("path") + "/file".
        
                
                    	  5.3+
	  Vor QF-Test Version 5.3 hatten Jython Skripte durch die Art, wie der Code von QF-Test an den Jython Compiler
	  übergeben wurde, weitere Probleme mit Zeichen außerhalb des 8-Bit Bereichs. Im Zuge der Behebung dieser
	  Probleme stellte es sich heraus, dass der beste Weg zur Behebung der Problem mit Jython String-Literalen die
	  Adaption eines bereits in Python 2 vorhanden Features ist, nämlich from future import
	  unicode_literals, um Jython Literale in QF-Test generell als Unicode-Strings zu behandeln. Dadurch sind
	  String-Literale nun in allen Skriptsprachen von QF-Test einheitlich und voll kompatibel mit Java-Strings, so
	  dass die Interaktion zwischen Jython und allem anderen in QF-Test viel natürlicher wird. Die neue Option
	  Literale (wörtliche Zeichenketten) in Jython sind Unicode (16-Bit wie in Java) bestimmt, ob String-Literale in Jython als Unicode-Strings
	  behandelt werden. Aus Kompatibilitätsgründen bleibt es bei 8-Bit Byte-Strings, falls QF-Test beim Start auf
	  eine ältere bestehende Systemkonfiguration trifft, andernfalls sind Unicode Literale nun der Standard.
        
                
Die empfohlenen Einstellungen für die Jython Optionen sind aktiviert für Literale (wörtliche Zeichenketten) in Jython sind Unicode (16-Bit wie in Java) und "utf-8" für Standard-Zeichenkodierung für Jython.
Behandlung von Problemen mit Jython und Zeichenkodierungen
                    	  Wie in den vorherigen Abschnitten beschrieben, verfügt Jython über zwei Arten von Strings, <type
	  'str'> für 8-Bit Byte-Strings und <type 'unicode'> für 16-Bit Unicode-Strings.
	  Literale kann ein 'b' vorangestellt werden (b"abc") um Byte-Strings zu erhalten und ein 'u'
	  (u"abc") für Unicode-Strings. Nicht näher gekennzeichnete Literale ("abc") sind
	  Unicode, falls die Option Literale (wörtliche Zeichenketten) in Jython sind Unicode (16-Bit wie in Java) aktiviert ist, andernfalls
	  Byte-Strings. Java-Strings aus einem Java-Funktionsaufruf wie rc.getStr("somevar")
	  sind immer Unicode-Strings.
	
                
Die folgenden Hinweise sollten Ihnen dabei helfen, Probleme mit Jython und Zeichenkodierungen zu minimieren:
- Schalten Sie die Option Literale (wörtliche Zeichenketten) in Jython sind Unicode (16-Bit wie in Java) ein und setzen Sie die Option Standard-Zeichenkodierung für Jython auf "utf-8".
- 
                        	    String-Literale mit $()-Expansion wie "$(varname)"waren immer schon problematisch und sollten durchrc.getStr("varname")oderrc.vars.varnameersetzt werden.
- 
                        	    Strings mit Windows-Dateinamen brauchen wegen der enthaltenen Rückstriche ('\') spezielle Behandlung.
	    In 8-Bit-Strings werden Rückstriche beibehalten, wenn sie keine Sonderfunktion wie '\t' für Tab oder '\n'
	    für einen Zeilenumbruch haben. In 16-Bit-Strings gibt es wesentlich mehr sogenannte Escape-Sequenzen mit
	    besonderer Bedeutung, die zu Syntaxfehlern oder unerwarteten Ergebnissen führen können. Probleme können
	    durch Verwendung von rc.getStr("filename")oderrc.vars.filename(siehe oben) und Voranstellen von 'r' (für "raw string") bei Angabe von Literalen, z.B.qftestDir = r"C:\Program Files\QFS\QF-Test", vermieden werden.
- 
                        	    Verwenden Sie grundsätzlich qf.printlnanstelle vonprint ..., da letzteres durch einen 8-Bit-Stream mit der Standardkodierung von Java (und im Falle eines SUT-Skript-Knotens zusätzlich mit der des Betriebssystems) durchgeschleift wird und dadurch internationale Zeichen leicht verloren gehen.
- 
                        	    Die Konvertierung von Objekten in Strings wurde in Jython traditionell via str(some_object)vorgenommen. Dastrder Typ von Byte-Strings ist, erzeugt dies immer einen Byte-String und erzwingt damit Enkodierung. Wenn Sie nicht ausdrücklich einen Byte-String benötigen, sollten Sie stattdessenunicode(some_object)verwenden.
- 
                        	    Das Jython-Modul typesbeinhaltet die Konstantentypes.StringTypeundtypes.UnicodeTypesowie die Listetypes.StringTypesmit beiden Typen. Letztere ist sehr hilfreich, um zu prüfen, ob ein Objekt von irgendeinem String-Typ ist, egal ob 8-Bit oder 16-Bit. Statt
 if type(some_object) == types.StringType
 sollte lieber
 if type(some_object) in types.StringTypes
 verwendet werden.
- 
                        	    In den wenigen Fällen, in denen Sie wirklich ein 8-Bit Byte-String Literal benötigen, setzen Sie ein 'b'
	    voran, z.B.
                        
 array.array(b'i', [1, 2, 3])
Und natürlich ist unser Support immer für Sie da.
                    11.4.6 Den Namen einer Java-Klasse ermitteln 
                
                        
                
                              Diese einfache Operation ist in Jython überraschend schwierig. Bei einem gegebenen Java
          Objekt würde man den Namen der Klasse einfach mittels
          obj.getClass().getName() bestimmen. Für manche Objekte funktioniert das
          auch in Jython, für andere scheitert es mit einer kryptischen Fehlermeldung, was recht
          frustrierend sein kann. Es geht immer dann schief, wenn die Klasse selbst auch eine
          getName Methode implementiert. Dies ist für AWT Component der
          Fall, so dass es für alle AWT/Swing Komponenten schwierig ist, den Namen ihrer Klasse zu
          ermitteln.
        
                
                              Die einzige Lösung, die zuverlässig funktioniert ist:
                    
                    
                              from java.lang import Class
                    
                              Class.getName(obj.getClass())
        
                
                              Da der Code nicht gerade intuitiv ist, haben wir ein neues Modul namens
          qf mit praktischen Methoden initiiert.
          Es ist automatisch verfügbar, so dass Sie nun einfach folgendes schreiben können:
                    
                    
                              qf.getClassName(obj).
        
                
                    11.4.7 Ein komplexes Beispiel 
                
                        
                Wir schließen diesen Abschnitt mit einem komplexen Beispiel ab, das Features von Jython und QF-Test kombiniert, um einen datengetriebenen Test durchzuführen. Wir gehen für dieses Beispiel von einer einfachen Tabelle mit den drei Spalten "Name", "Age" und "Address" aus, die mit Werten gefüllt werden soll, die aus einer Datei gelesen werden. Die Datei soll dabei im "Comma-Separated-Values" Format vorliegen, mit '|' als Trennzeichen, eine Zeile pro Tabellenzeile, z.B.:
                              John Smith|45|Some street, some town
                    
                              Julia Black|35|Another street, same town
        
                
Das Beispiel testet die Funktionalität des SUT neue Tabellenzeilen zu erstellen. Dabei kommt eine QF-Test Prozedur zum Einsatz, die 3 Parameter erwartet - "name", "age" und "address" - und mit diesen eine neue Tabellenzelle anlegt und füllt. Im Jython-SUT-Skript wird die Datei mit den Werten eingelesen und geparst. In einer Schleife wird über die Datensätze iteriert und für jede zu erstellende Tabellenzeile die Prozedur aufgerufen. Der Name für die Datei wird in der QF-Test Variable namens "filename" übergeben. Wenn das Füllen der Tabelle abgeschlossen ist, wird der Endzustand der Tabelle mit den eingelesenen Werten verglichen, um sicher zu gehen, dass alles geklappt hat.
import string
data = []
# read the data from the file
fd = open(rc.getStr("filename"), "r")
line = fd.readline()
while line:
    # remove whitespace
    line = string.strip(line)
    # split the line into separate fields
    # and add them to the data array
    if len(line) > 0:
        data.append(string.split(line, "|"))
    line = fd.readline()
# now iterate over the rows
for row in data:
    # call a qftest procedure to create
    # one new table row
    rc.callProcedure("table.createRow",
                     {"name": row[0], "age": row[1],
                      "address": row[2]})
# verify that the table-rows have been filled correctly
table = rc.getComponent("tabAddresses")
# check the number of rows
if table.getRowCount() != len(data):
    rc.logError("Row count mismatch")
else:
    # check each row
    for i in range(len(data)):
        if str(table.getValueAt(i, 0)) != data[i][0]:
            rc.logError("Name mismatch in row " + str(i))
        if str(table.getValueAt(i, 1)) != data[i][1]:
            rc.logError("Age mismatch in row " + str(i))
        if str(table.getValueAt(i, 2)) != data[i][2]:
            rc.logError("Address mismatch in row " + str(i)) 
                    Natürlich dient obiges Beispiel nur zur Anschauung. Es ist viel zu komplex, um halbwegs komfortabel in QF-Test editiert werden zu können. Außerdem sind zu viele Dinge fest verdrahtet, so dass es mit der Wiederverwendbarkeit nicht weit her ist. Für eine echte Anwendung würde man den Code zum Einlesen und Parsen der Datei parametrisieren und in ein Modul auslagern, ebenso den Code zur Verifikation der Tabelle.
                              Dies geschieht im folgenden Jython-Skript mit den Methoden
          loadTable zum Lesen der Daten aus der Datei und
          verifyTable zum Überprüfen der Tabelle.
          Es wird in einem Modul namens csvtable.py abgespeichert.
          Ein Beispiel dafür finden Sie in
          qftest-9.0.6/doc/tutorial/csvtable.py.
          Zur Erläuterung genügt folgende vereinfachte Version:
        
                
import string
def loadTable(file, separator="|"):
    data = []
    fd = open(file, "r")
    line = fd.readline()
    while line:
        line = string.strip(line)
        if len(line) > 0:
            data.append(string.split(line,separator))
        line = fd.readline()
    return data
def verifyTable(rc, table, data):
    ret = 1
    # check the number of rows
    if table.getRowCount() != len(data):
        if rc:
            rc.logError("Row count mismatch")
        return 0
    # check each row
    for i in range(len(data)):
        row = data[i]
        # check the number of columns
        if table.getModel().getColumnCount() != len(row):
            if rc:
                rc.logError("Column count mismatch " +
                            "in row " + str(i))
            ret = 0
        else:
            # check each cell
            for j in range(len(row)):
                val = table.getModel().getValueAt(i, j)
                if str(val) != row[j]:
                    if rc:
                        rc.logError("Mismatch in row " +
                                    str(i) + " column " +
                                    str(j))
                    ret = 0
    return ret 
                    Der obige Code sollte Ihnen bekannt vorkommen. Er ist eine verbesserte Version von Teilen von Beispiel 11.22. Ist dieses Modul installiert, vereinfacht sich der Code, der in QF-Test geschrieben werden muss, wie folgt:
import csvtable
# load the data
data = csvtable.loadTable(rc.getStr("filename"))
# now iterate over the rows
for row in data:
    # call a qftest procedure to create
    # one new table row
    rc.callProcedure("table.createRow",
                     {"name": row[0], "age": row[1],
                      "address": row[2]})
# verify that the table-rows have been filled correctly
table = rc.getComponent("tabAddresses")
csvtable.verifyTable(rc, table, data) 
                    
                    11.5 Groovy-Skripting 
                
                      
                
                            Groovy ist eine weitere etablierte Skriptsprache für die Java-Platform. Sie wurde von
        James Strachan and Bob McWhirter im Jahre 2003 entwickelt. Im Grunde ist alles was man für
        Groovy braucht, eine Java-Laufzeitumgebung (JRE) und die Datei
        groovy-all.jar. Diese Bibliothek enthält sowohl einen Compiler, um Java .class
        Dateien zu erstellen, wie auch die entsprechende Laufzeitumgebung, um diese Klassen in der
        Java Virtual Machine (JVM) auszuführen. Man kann sagen, Groovy ist Java mit einer
        zusätzlichen .jar Datei. Im Gegensatz zu Java ist Groovy allerdings eine
        dynamische Sprache, was bedeutet, dass das Verhalten von Objekten erst zur Laufzeit
        ermittelt wird. Außerdem können Klassen auch direkt aus dem Skriptcode geladen werden,
        ohne erst Class-Dateien erzeugen zu müssen. Schließlich lässt sich Groovy auch leicht in
        Java-Anwendungen wie QF-Test einbetten.
      
                
Die Groovy Syntax ist ähnlich der von Java, vielleicht ausdrucksstärker und leichter zu lesen. Wenn man von Java kommt, kann man sich dem Groovy Stil nach und nach annähern. Wir können hier natürlich nicht die Sprache Groovy in allen Details besprechen, dazu sei auf die Groovy Homepage http://groovy-lang.org/ oder das exzellente Buch "Groovy in Aktion" von Dierk Koenig u.a. verwiesen. Vielleicht können aber die folgenden Hinweise einem Java-Programmierer beim Einstieg in Groovy helfen.
- Das Semikolon ist optional, solange eine Zeile nur ein Statement enthält.
- 
                                  Klammern sind manchmal optional, zum Beispiel bedeutet println 'hello qfs'dasselbe wieprintln('hello qfs').
- 
                                  Anstelle von for (int i = 0; i < len; i++) { ... }verwende manfor (i in 0..<len) { ... }.
- 
                                  Die folgenden Importe werden bei Groovy standardmäßig vorgenommen: java.lang.*, java.util.*, java.io.*, java.net.*, groovy.lang.*, groovy.util.*, java.math.BigInteger, java.math.BigDecimal.
- Alles ist ein Objekt, sogar Integer oder Boolean Werte wie '1' oder 'true'.
- 
                                  Anstelle von Getter- und Setter-Methoden wie obj.getXxx()kann man einfachobj.xxxverwenden.
- 
                                  Der Operator ==prüft auf Gleichheit statt auf Identität, so dass Sieif (somevar == "somestring")stattif (somevar.equals("somestring"))verwenden können. Um auf Identität zu prüfen, gibt es die Methodeis().
- 
                                  Variablen haben einen dynamischen Typ, wenn sie mit dem Schlüsselwort defdeklariert werden.def x = 1zum Beispiel erlaubt es, der Variablenxspäter auch einenStringzuzuweisen.
- 
                                  Arrays werden etwas anders als in Java definiert, z. B. int[] a = [1, 2, 3]oderdef a = [1, 2, 3] as int[]. Mitdef a = [1, 2, 3]wird in Groovy eine Liste definiert.
- 
                                  Groovy erweitert die Java-Bibliothek indem für viele Klassen zusätzliche Methoden
          definiert werden. So kann in einem Groovy-Skript etwa die Methode
          isInteger()auf einStringObjekt angewendet werden. Diese Erweiterungen werden als GDK bezeichnet (analog zu JDK in Java). Eine Liste der GDK-Methoden für ein Objektobjliefert der Ausdruckobj.class.metaClass.metaMethods.nameoder - übersichtlicher - das folgende Beispiel:
import groovy.inspect.Inspector
def s = 'abc'
def inspector = new Inspector(s)
def mm = inspector.getMetaMethods().toList().sort() {
    it[Inspector.MEMBER_NAME_IDX] }
for (m in mm) {
    println(m[Inspector.MEMBER_TYPE_IDX] + ' ' +
            m[Inspector.MEMBER_NAME_IDX] +
            '(' + m[Inspector.MEMBER_PARAMS_IDX] + ')')
} 
                    String Objekt
                    - 
                                  Innere Klassen werden nicht unterstützt. In den meisten Fällen können stattdessen
          Closures verwendet werden. Eine Closureist ein Object, das einen Code-Schnipsel repräsentiert. Sie kann Parameter haben und auch ein Wert zurückliefern. Genau wie ein Block wird eineClosurein geschweiften Klammern definiert. Blöcke gibt es nur im Zusammenhang mitclass,interface, statischer oder Objekt-Initialisierung, Methodenrümpfen,if,else,synchronized,for,while,switch,try,catchundfinally. Jedes andere Vorkommen von{...}ist eineClosure. Als Beispiel schauen wir uns die GDK-MethodeeachFileMatchder KlasseFilean. Sie hat zwei Parameter: einen Filter (z. B. einPatternObjekt) und eineClosure. DieseClosurehat selbst auch einen Parameter: einFileObject, das die gerade gefundene Datei repräsentiert.
def dir = rc.getStr('qftest', 'suite.dir')
def pattern = ~/.*\.qft/
def files = []
new File(dir).eachFileMatch(pattern) { file ->
    files.add(file.name)
}
files.each {
    // Auf ein einzelnes Closure-Argument kann mit "it" zugegriffen werden.
    rc.logMessage(it)
} 
                    - 
                                  Mit Listen (List) und Dictionaries (Map) lässt es sich in Groovy viel leichter arbeiten als in Java.
def myList = [1, 2, 3]
assert myList.size() == 3
assert myList[0] == 1
myList.add(4)
def myMap = [a:1, b:2, c:3]
assert myMap['a'] == 1
myMap.each {
    this.println it.value
} 
                    
                    11.5.1 Groovy Packages 
                
                        
                
                              Genau wie Java-Klassen werden Groovy-Skriptdateien (.groovy) in
          Packages organisiert. Diejenigen, welche suiteübergreifend Anwendung finden, stellt
          man am besten in den groovy-Ordner unterhalb des QF-Test
          Wurzelverzeichnisses. Dateien bzw. Packages, die speziell für eine Testsuite
          entwickelt worden sind, können auch im Verzeichnis der Testsuite abgelegt werden. Das
          versionsspezifische Verzeichnis qftest-9.0.6/groovy ist für
          Groovy-Dateien reserviert, die von Quality First Software GmbH bereitgestellt werden.
        
                
package my
class MyModule
{
    public static int add(int a, int b)
    {
        return a + b
    }
} 
                    MyModule.groovy
                    
                              Die Datei MyModule.groovy könnte etwa im Unterverzeichnis
          my unterhalb des Testsuite-Verzeichnisses abgespeichert werden. Die
          Methode add aus MyModule kann dann folgendermaßen aufgerufen
          werden:
        
                
import my.MyModule as MyLib assert MyLib.add(2, 3) == 5
                              Dieses Beispiel demonstriert gleichzeitig noch ein weiteres Groovy Feature: Type
          Aliasing. Indem import und as zusammen verwendet
          werden, kann man eine Klasse über einen Namen eigener Wahl referenzieren.
        
                
                    11.6 JavaScript-Skripting 
                
                      
                JavaScript hat sich vor allem im Bereich der Webentwicklung durchgesetzt und ist dort eine sehr beliebte Programmiersprache. QF-Test unterstützt ECMAScript, das entwickelt wurde um einen Standard für JavaScript bereitzustellen.
Um JavaScript verwenden zu können muss QF-Test mindestens mit Java 8 ausgeführt werden.
Dabei muss der ECMAScript 6 Standard in den JavaScript-Skripten verwendet werden. QF-Test führt automatisch eine interne Übersetzung auf den ECMAScript 5 Standard durch. Im Fehlerfall wird der übersetzte Code im Protokoll im Skript-Knoten aufgeführt, falls dieser vom Original-Code abweicht.
Einige Besonderheiten von JavaScript gegenüber anderen Skriptsprachen.
- 
                                  Es gibt zwei verschiedene null-Werte: undefinedundnull. Eine Variable istundefined, wenn sie keinen Wert besitzt.nullist ein beabsichtigter Null-Wert der zugewiesen werden muss.
- 
                                  Der Operator ==prüft auf Gleichheit statt auf Identität, so dassif (3 == "3")"true" ergibt. Um auf Identität, dass heißt Gleichheit von Typ und Wert beider Operanten, zu prüfen, gibt es den===Operator.
- 
                                  Variablen haben einen dynamischen Typ, wenn sie mit dem Schlüsselwort letdeklariert werden.let x = 1zum Beispiel erlaubt es, der Variablenxspäter auch einenStringzuzuweisen. Konstanten werden mitconstdefiniert.
                    11.6.1 Module 
                
                        
                
                              Auch in JavaScript können häufig benötigte Funktionen in Module ausgelagert werden.
          Diese müssen analog zu Jython bzw. Groovy in das javascript-Verzeichnis im QF-Test Wurzelverzeichnis gelegt werden.
        
                
                              Im folgenden Beispiel werden die Funktionen des Moduls moremath.js
          ausgelagert. Zunächst der Aufbau des Moduls:
        
                
                              
                    var fibonacci = function(n) {
    return n < 1 ? 0
                 : n <= 2 ? 1
                 : fibonacci(n - 1) + fibonacci(n - 2);
}
function sumDigits(number) {
    var str = number.toString();
    var sum = 0;
    for (var i = 0; i < str.length; i++) {
        sum += parseInt(str.charAt(i), 10);
    }
    return sum;
}
// Module exports (Node.js style)
exports.fibonacci = fibonacci;
exports.sumDigits = sumDigits; 
                        moremath.js
                        
                              In dem Modul moremath.js sind zwei Funktionen definiert:
          fibonacci und sumDigits. fibonacci berechnet den Wert der Fibonacci-Zahl an der Stelle n
          und sumDigits bildet die Quersumme.
          
                    
                                    Jede Funktion muss exportiert werden, damit sie für den Import zur Verfügung steht.
            Dies geschieht mit der an Node.js angelehnten Funktion exports.
          
                    
                                    Im Skript-Knoten kann nun der folgende Code verwendet werden um auf die Funktionen des Moduls moremath.js zuzugreifen:
          
                    
moremath = require('moremath');
              console.log(moremath.fibonacci(13));
              console.log(moremath.sumDigits(123)); 
                        Verwendung des moremath.js-Moduls
                        Module die von QF-Test bereitgestellt werden, können über die import-Funktion importiert werden.
import {Autowin} from 'autowin';
            Autowin.doClickHard(10, 10, true); 
                    Verwendung des autowin-Moduls
                    Java-Klassen können ebenfalls über das import Statement importiert werden.
import {File} from 'java.io'; 
                    Import von Java-Klassen
                    
                              Es ist auch möglich, mit der require-Funktion npm-Module zu importieren.
          Diese werden im nächsten Abschnitt beschrieben.
        
                
                    11.6.1.1 npm-Module 
                
                          
                
                                npm ist ein Paketmanager für JavaScript der über 350.000 Pakete zur Verfügung stellt. Unter der Webseite https://www.npmjs.com/  können die vorhandenen Pakete durchsucht werden.
            Es ist möglich, in einem QF-Test Skript, installierte npm-Module zu verwenden.
            Diese müssen im javascript-Verzeichnis des QF-Test Wurzelverzeichnisses installiert werden.
            Mit dem Kommando npm install underscore wird das npm-Modul underscore
            über die Konsole des Betriebssystems installiert. Dieses kann nun in den Skript-Knoten verwendet werden.
          
                
                                Es gibt npm-Module, die nicht mit Nashorn kompatibel sind.
            Da beispielsweise einige Funktionen verwendet werden, die nicht vom ECMAScript Standard spezifiziert werden
          
                
_ = require('underscore');
              func = function(num){ return num % 2 == 0; }
              let evens = _.filter([1, 2, 3, 4, 5, 6], func);
              console.log(evens); 
                    Verwendung des underscore-Moduls
                    
                    11.6.2 Ausgaben 
                
                        
                
                              Neben console.log() wurde für Ausgaben ins Terminal in QF-Test eine zusätzliche print-Methode definiert.
        
                
print([1,2,3,4]);
Ausgabe eines Arrays
                    
                    11.6.3 Ausführung 
                
                        
                Die JavaScript-Skripte werden auf Server- bzw. SUT-Seite nicht im Browser ausgeführt, sondern in der Nashorn-Engine. Dies ermöglicht die Ausführung von ECMAScript in der JVM.