16
Testausführung

Die Ausführung von Tests ist unter verschiedenen Blickwinkeln zu betrachten: Zum einen werden Tests während der Entwicklung aus der laufenden QF-Test Anwendung heraus ausgeführt, um die Funktionstüchtigkeit der Tests zu überprüfen. Diese Situation wurde bereits in Abschnitt 5.2 behandelt; sie reduziert sich im Wesentlichen auf den Aufruf von »Wiedergabe«-»Start« im QF-Test Hauptmenü.

Auf der anderen Seite sollen die entwickelten Tests regelmäßig ausgeführt werden, um die Stabilität der getesteten Software sicherzustellen - etwa bei nächtlichen Regressionstests. Anstatt die auszuführende Testsuite mit QF-Test zu öffnen und über die Benutzeroberfläche auszuführen, ist es hier viel praktischer, den Testlauf von der Kommandozeile im sogenannten Batchmodus zu starten. Diese Art der Testausführung bildet den ersten Abschnitt dieses Kapitels (Testausführung im Batchmodus).

In manchen Situationen, etwa wenn der Testlauf auf einem entfernten Rechner ausgeführt werden soll, kommt eine weitere Variante ins Spiel: der Daemonmodus. Hier dient eine laufende QF-Test Instanz sozusagen als Ausführungsorgan für Tests. Mit diesem Thema wird sich der zweite Abschnitt befassen (Testausführung im Daemonmodus).

Hinweise zur Integration von QF-Test mit Build Tools wie ant, maven oder Hudson/Jenkins finden Sie in Kapitel 20.

16.1
Testausführung im Batchmodus

Grundlage für die Ausführung von Tests über die Kommandozeile bildet eine Vielzahl von Argumenten, mit denen QF-Test im Batchmodus gestartet werden kann. Anschließend werden exemplarisch einige davon behandelt; eine Übersicht über alle Optionen findet man in Kapitel 32.

Die nachfolgenden Beispiele sind für Windows geschrieben, lassen sich aber leicht auf Linux übertragen. Neben den Pfadangaben unterscheidet sich die Syntax nur noch im Bezug auf die Platzhalter (Abschnitt 32.2.4): Unter Linux kann neben der Form +X auch %X verwendet werden. Unter Windows gibt es außerdem neben der GUI-Anwendung qftest.exe auch eine Konsolen-Variante qftestc.exe. Diese wartet, bis die Ausführung von QF-Test beendet ist und gibt u. a. auch print-Ausgaben von einem 'Server Skript' aus.

16.1.1
Verwenden der Kommandozeile

Der einfachste Aufruf von QF-Test, um einen Test auszuführen, sieht so aus:

qftest -batch -run c:\mysuites\suiteA.qft
Beispiel 16.1:  Testausführung von der Kommandozeile

Das Argument -batch sorgt dafür, dass QF-Test ohne graphische Benutzeroberfläche gestartet wird. Das zweite Argument, -run, sagt QF-Test, dass ein Test ausgeführt werden soll. Den Abschluss der Kommandozeile bildet die auszuführende Testsuite.

Hinweis Das Argument -run ist optional, d. h. die Testausführung ist als Standard für den Batchmodus definiert.

Führt man die obige Anweisung aus, werden alle 'Test', 'Testfall' und 'Testfallsatz' Knoten nacheinander ausgeführt, die in der Testsuite suiteA.qft auf der obersten Ebene (d. h. direkt unterhalb des 'Testsuite' Knotens) definiert sind. Nach Beendigung des Testlaufs findet man im aktuellen Verzeichnis, unter dem gleichen Namen wie die Testsuite, eine Protokolldatei, der man das Testergebnis entnehmen kann.

Durch Angabe von -nolog kann die Erstellung der Protokolldatei auch unterdrückt werden. Das macht aber wahrscheinlich nur dann Sinn, wenn man seinen Test um eigene Protokollausgaben bereichert hat, die etwa in eine Datei geschrieben werden. Ansonsten lieferte nur noch der Rückgabewert von QF-Test einen Hinweis auf den Ausgang des Testlaufs: 0 bedeutet, dass der Test erfolgreich absolviert wurde, ein positiver Wert hingegen ist die Folge von Warnungen, Fehlern oder gar Exceptions (siehe Abschnitt 32.3).

In der Regel möchte man also wohl ein Protokoll des Testlaufs erstellen und dieses an einer bestimmten Stelle im Dateisystem ablegen. Das geht über den Parameter -runlog:

qftest -batch -compact -runlog c:\mylogs\+b c:\mysuites\suiteA.qft
Beispiel 16.2:  Testausführung mit Protokolldatei

Nun wird eine Protokolldatei suiteA.qrz im angegebenen Verzeichnis c:\mylogs erstellt. Der Platzhalter +b sorgt dafür, dass sie den Namen von der Testsuite übernimmt. Durch die Angabe von -compact wird erreicht, dass nur die wichtigsten Knoten in die Protokolldatei übernommen werden, also nur solche, die für den Report und gegebenenfalls die Fehleranalyse benötigt werden. Insbesondere bei sehr umfangreichen Tests kann dieser Parameter helfen, den Anspruch an Arbeitsspeicher in Grenzen zu halten. Mittlerweile bietet QF-Test allerdings, ebenfalls um den Speicherverbrauch zu reduzieren, auch die Möglichkeit, Protokolldateien zu splitten. Nähere Informationen dazu findet man in Abschnitt 9.1.

Hinweis Ob die Datei hier tatsächlich als komprimiertes Protokoll (das ist vom obigen "compact" zu unterscheiden) mit der Endung .qrz erstellt wird, hängt von den Einstellungen ab. Um ein bestimmtes Format zu erzwingen, kann die Endung explizit angegeben werden, also z. B. -runlog c:\mylogs\+b.qrl, wenn unkomprimiertes XML gewünscht wird.

Manchmal möchte man vielleicht nicht die komplette Testsuite, sondern nur bestimmte Testfälle ausführen. Über den Parameter -test lässt sich ein ganz bestimmter Knoten zur Ausführung festlegen:

qftest -batch -runlog c:\mylogs\+b -test "Mein Test" c:\mysuites\suiteA.qft
Beispiel 16.3:  Ausführung eines bestimmten Knotens

Die Angabe "Mein Test" bezieht sich dabei auf das Attribut 'Id' des auszuführenden Knotens; bei 'Testfall' oder 'Testfallsatz' Knoten kann auch deren qualifizierter Name angegeben werden. Sollen mehrere Knoten ausgeführt werden, kann das Argument -test <Id> auch mehrfach angegeben werden. Neben einer Knoten-'Id' versteht -test auch die Angabe eines numerischen Index. So würde etwa -test 0 den ersten Knoten unterhalb von 'Testsuite' ausführen.

Das Protokoll liefert eine eher technische Sicht auf den Ablauf eines Tests; es ist vor allem nützlich, um während eines Testlaufs aufgetretene Fehler zu analysieren (vgl. Abschnitt 9.1). Eine übersichtliche Darstellung über die ausgeführten Testfälle und eventuell aufgetretene Fehler bietet hingegen der Report. Er wird aus der Protokolldatei erstellt und liegt anschließend im XML- und/oder HTML-Format vor. Der Report kann also auch nachträglich aus einer (oder mehreren) Protokolldateien erstellt werden (vgl. Kapitel 15). Um gleich bei der Testausführung dafür zu sorgen, verwendet man -report:

qftest -batch -runlog c:\mylogs\+b -report c:\mylogs\rep_+b_+y+M+d+h+m c:\mysuites\suiteA.qft
Beispiel 16.4:  Testausführung mit Reportgenerierung

Hier werden die XML- und HTML-Reportdateien in einem Verzeichnis erstellt, dessen Name sowohl die Testsuite wie auch Datum und Uhrzeit enthält, etwa: c:\mylogs\rep_suiteA_0806042152. Ersetzt man das Argument -report durch -report.xml bzw. -report.html, so wird nur der XML- bzw. nur der HTML-Report erstellt.

Testfälle sind oft parametrisiert, das heißt, sie verwenden Variablen, deren Werte den Testablauf bestimmen. Ist beispielsweise im 'Testsuite' Knoten eine Variable myvar definiert, kann deren Vorgabewert zur Testausführung überschrieben werden:

qftest -batch -variable myvar="Value from command line" -runlog c:\mylogs\+b c:\mysuites\suiteA.qft
Beispiel 16.5:  Testausführung mit Variablen

Sollen mehrere Variablen gesetzt werden, kann -variable <name>=<wert> auch mehrfach angegeben werden.

16.1.2
Windows Befehlsskript

Die Möglichkeit, Tests über die Kommandozeile ausführen zu können, bildet die Grundlage zur einfachen Integration von QF-Test in bestehende Testmanagement-Systeme (siehe Anbindung an Testmanagementtools). Wer hingegen kein Testmanagement-System betreibt, mag es vielleicht praktisch finden, das Kommando zur Testausführung in ein Skript einzubetten. Ein einfaches Windows Befehlsskript (qfbatch.bat) könnte zum Beispiel wie folgt aussehen:

@echo off
setlocal
if "%1" == "" (
    echo Usage: qfbatch Testsuite
    goto end
) else (
    set suite=%~f1
)
set logdir=c:\mylogs
pushd c:\programs\qftest\qftest-3.4.7\bin

@echo on
.\qftest -batch -compact -runlog %logdir%\+b %suite%
@echo off

if %errorlevel% equ 0 (
  echo Test terminated successfully
  goto end
)
if %errorlevel% equ 1 (
  echo Test terminated with warnings
  goto end
)
if %errorlevel% equ 2 (
  echo Test terminated with errors
  goto end
)
if %errorlevel% equ 3 (
  echo Test terminated with exceptions
  goto end
)
if %errorlevel% leq -1 (
  echo Error %errorlevel%
  goto end
)

:end
popd
Beispiel 16.6:  Befehlsskript qfbatch.bat zur Ausführung einer Testsuite

Nun braucht man nur noch das Skript auszuführen, mit dem Dateinamen der auszuführenden Testsuite als Parameter. Der Rest geht von alleine: Die Testsuite wird ausgeführt, die Protokolldatei im Verzeichnis logdir abgelegt und abschließend gibt das Skript in Abhänigigkeit vom QF-Test Rückgabewert eine Statusmeldung aus.

3.0+16.1.3
Groovy

Seit Version 3 ist die Sprache Groovy Bestandteil von QF-Test (siehe Kapitel 13). Hauptsächlich gedacht ist sie zum Schreiben von Server- und SUT-Skripten, aber Groovy kann ebenso wie Jython auch außerhalb von QF-Test verwendet werden. Diese Sprache ist sicher gut geeignet, um sich selbst ein kleines Management-System zur Testautomatisierung zu erstellen. Mit Groovy lässt sich übrigens auch der Einsatz von Ant vereinfachen, denn statt mit klobigen XML-Dateien, über die sich zudem etwa Bedingungen nur schwer umsetzen lassen, kann man dank AntBuilder mit übersichtlichem Groovy-Code arbeiten. Das folgende Beispiel kommt allerdings ohne Ant aus:

def suite = ''
if (args.size() == 0) {
    println 'Usage: groovy QfExec Testsuite'
    return
}
else {
    suite = args[0]
}

def qftestdir = 'c:\\programs\\qfs\\qftest\\qftest-3.4.7'
def qftest = qftestdir + '\\bin\\qftest.exe'
def command = "$qftest -batch -compact -runlog c:\\mylogs\\+b \"$suite\""
def printStream = { stream ->
    while (true) {
        try {
            stream.eachLine { println it }
        } catch (IOException) {
            break
        }
    }
}

println "Running command: $command"
def proc = command.execute()
new Thread().start() { printStream(proc.in) }
new Thread().start() { printStream(proc.err) }
proc.waitFor()

switch (proc.exitValue()) {
    case '0': println 'Test terminated successfully'; break
    case '1': println 'Test terminated with warnings'; break
    case '2': println 'Test terminated with errors'; break
    case '3': println 'Test terminated with exceptions'; break
    default: println "Error ${proc.exitValue()}"
}
Beispiel 16.7:  Groovy Skript QfExec.groovy zur Ausführung einer Testsuite

Sollte Groovy unabhängig von QF-Test auf Ihrem Rechner installiert sein, kann die Beispiel-Testsuite einfach mit groovy QfExec c:\mysuites\suiteA.qft ausgeführt werden. Andernfalls kann auch die Groovy jar-Datei aus der QF-Test Installation verwendet werden. Am besten nutzt man in diesem Fall zur Ausführung von Groovy wieder ein Befehlsskript:

@echo off
setlocal
if "%1" == "" (
    echo Usage: qfexec Testsuite
    goto end
)
set qftestdir=c:\programs\qftest\qftest-3.4.7
set scriptfile=QfExec.groovy
java -cp %qftestdir%/lib/groovy-all.jar groovy.ui.GroovyMain %scriptfile% %*
:end
Beispiel 16.8:  Befehlsskript qfexec.bat zur Ausführung von Groovy (hier QfExec.groovy)

Die Testsuite kann nun mit qfexec c:\mysuites\suiteA.qft ausgeführt werden.

16.2
Testausführung im Daemonmodus

Im Daemon Modus gestartet lauscht QF-Test auf RMI Verbindungen und stellt darüber ein Interface für die verteilte Ausführung von Tests zur Verfügung. Dies kann für die Testdurchführung in einem verteilten Lasttest Szenario (vgl. Kapitel 21) ebenso hilfreich sein wie für die Integration mit vorhandenen Testmanagement- oder Testdurchführungs-Werkzeugen (vgl. Kapitel 19).

16.2.1
Starten des Daemons

!!! Warnung !!! Im Daemon Modus akzeptiert QF-Test Verbindungen von jedem, der Netzwerk-Zugang zum Rechner und dem entsprechenden Port hat. Es findet keine Überprüfung von Name oder Zugriffsrechten statt. Verwenden Sie dieses Feature nicht außerhalb einer sicheren Umgebung.

Um mit dem Daemon arbeiten zu können, muss er zunächst auf irgendeinem Rechner im Netzwerk gestartet werden (das kann natürlich auch localhost sein):

qftest -batch -daemon -daemonport 12345
Beispiel 16.9:  Starten des QF-Test Daemon

Lässt man das Argument -daemonport weg, lauscht der Daemon auf Port 3543. Ob der Daemon erfolgreich gestartet wurde, kann man z. B. mit dem Programm netstat prüfen:

Windowsnetstat -a -p tcp -n | findstr "12345"

Linuxnetstat -a --tcp --numeric-ports | grep 12345

Will man den Daemon auf einem entfernten Rechner starten, bieten sich zum Beispiel ssh oder VNC an. Ob und wie das ggf. in Ihrem Netzwerk funktioniert, weiß der Netzwerkadministrator. Um die folgenden Beispiele nachzuvollziehen, reicht aber auch ein lokaler Dämon.

3.0+16.2.2
Steuern des Daemons über die QF-Test Kommandozeile

Die einfachste Möglichkeit, den lauschenden Daemon anzusprechen bietet die Kommandozeile, indem man QF-Test im sogenannten calldaemon Modus startet. Das folgende Beispiel prüft, ob der Daemon an der angegebenen Host-/Portkombination erreichbar ist:

qftestc -batch -calldaemon -daemonhost localhost -daemonport 12345 -ping && echo Daemon is running
Beispiel 16.10:  Pingen eines QF-Test Daemon

Anders als das obige netstat Kommando funktioniert -ping auch über Rechnergrenzen hinweg (auf dem lokalen Rechner kann man das Argument -daemonhost einfach weglassen).

Auf ähnliche Weise wie man eine Testsuite im Batchmodus ausführt, kann man nun einen Dämon dazu bringen, einen bestimmten Testfall auszuführen und ein Protokoll des Testlaufs zu schreiben:

qftest -batch -calldaemon -daemonhost somehost -daemonport 12345 -runlog c:\mylogs\+b -suitedir c:\mysuites suiteA.qft#"Mein Testfall"
Beispiel 16.11:  Ausführung eines Testfalls mit dem QF-Test Daemon

Hinweis Anders als im Batchmodus wird beim Verwendung eines Daemons ein 'Testfall' oder ein 'Testfallsatz' (andere Knotentypen sind nicht erlaubt) stets über seinen qualifizierten Namen angesprochen, z.B. "Mein Testfallsatz.Mein Testfall" (zur Erinnerung: Beim Batchmodus wird -test <Id> verwendet). Will man die komplette Testsuite suiteA.qft ausführen, so lässt man die Angabe des Testfalls einfach weg oder schreibt suiteA.qft#..

Wird der Daemon auf einem entfernen Rechner gestartet, gibt man diesen bei der Ausführung von calldaemon über den Parameter -daemonhost explizit an (Vorgabe ist -daemonhost localhost). Man beachte, dass sich dabei der Parameter -suitedir auf den entfernten Rechner bezieht (auf dem der Daemon läuft), während -runlog eine lokale Datei bezeichnet.

3.4+ Gerade dann, wenn man die Testausführung nicht so leicht beobachten kann, bietet es sich an, zusätzlich das Argument -verbose anzugeben, um so Statusinformationen auf der Konsole angezeigt zu bekommen (auf Windows muss dazu qftestc verwendet werden).

Ein Daemon, lokal oder entfernt, lässt sich über das calldaemon Kommando -terminate wieder beenden:

qftest -batch -calldaemon -daemonport 12345 -daemonhost localhost -terminate
Beispiel 16.12:  Beenden eines QF-Test Daemon

Eine vollständige Übersicht über die calldaemon-Parameter finden Sie im Kapitel Kommandozeilenargumente und Rückgabewerte.

16.2.3
Steuern des Daemons über die Daemon API

Das Ansprechen des Daemons über die QF-Test Kommandozeile ist auf der einen Seite ganz praktisch, auf der anderen jedoch bietet sie nur eingeschränkte Möglichkeiten. Um die daemonischen Fähigkeiten voll auszureizen, muss man sich der Daemon-API bedienen. Wir werden diese hier beispielhaft vorstellen, die vollständige Schnittstelle ist in Kapitel 40 beschrieben.

Für erste Experimente mit der Daemon-API bietet sich ein 'Server Skript' Knoten an:

from de.qfs.apps.qftest.daemon import DaemonRunContext
from de.qfs.apps.qftest.daemon import DaemonLocator

host = "localhost"
port = 12345
# Leading r means raw string to allow normal backslashes in the path string.
testcase = r"c:\mysuites\suiteA.qft#Mein Testfall"
timeout = 60 * 1000

def calldaemon(host, port, testcase, timeout=0):
    daemon = DaemonLocator.instance().locateDaemon(host, port)
    trd = daemon.createTestRunDaemon()
    context = trd.createContext()
    context.runTest(testcase)
    context.waitForRunState(DaemonRunContext.STATE_FINISHED, timeout)
    result = context.getResult()
    log = context.getRunLog()
    rc.addDaemonLog(log)
    context.release()
    return result

result = calldaemon(host, port, testcase, timeout)
rc.logMessage("Result from daemon: %d" %result)
Beispiel 16.13:  Daemon-API im 'Server Skript'

Das Skript zeigt den grundlegenden Mechanismus der Daemon-Ansteuerung:

Hinweis Das Beispielskript verzichtet aus Gründen der Übersichtlichkeit auf jegliche Fehlerbehandlung. Gerade beim Arbeiten mit einem Daemon sollte man aber jeden Aufruf auf eventuelle Fehler überprüfen.

Hinweis Ein Nachteil ist mit der Daemon-Steuerung aus einem 'Server Skript' verbunden: Es wird eine zusätzliche QF-Test Lizenz benötigt, um den Skript-Knoten interaktiv oder im Batchmodus auszuführen. Das gilt allerdings nicht, wenn man den oben beschriebenen calldaemon Modus verwendet oder sich außerhalb von QF-Test mit dem Daemon verbindet (siehe unten).

Die Verwendung der Daemon-API ist nicht auf 'Server Skripte' beschränkt. Außerhalb von QF-Test kann der Daemon über ein Java-Programm oder, einfacher noch, ein Groovy-Skript angesprochen werden. Das folgende Groovy-Beispiel arbeitet mit mehreren Daemon-Instanzen und kann daher auch als Ausgangspunkt für Lasttests dienen. Nehmen wir an, dass auf verschiedenen Rechnern jeweils ein QF-Test Daemon gestartet wurde. Jeder der Daemonen soll einen bestimmten Testfall ausführen und für jeden der Testläufe soll ein Protokoll abgelegt werden (daemon1.qrl, ..., daemonN.qrl). Die Testsuite mit dem auszuführenden Testfall sei allen Daemon-Instanzen über ein Netzlaufwerk (hier z:) zugänglich.

import de.qfs.apps.qftest.daemon.DaemonLocator
import de.qfs.apps.qftest.daemon.DaemonRunContext

def testcase = "z:\\mysuites\\suiteA.qft#Mein Testfall"
def logfile = "c:\\mylogs\\daemon"
def timeout = 120 * 1000

def daemons = DaemonLocator.instance().locateDaemons(10000)
def contexts = []
// Start tests
for (daemon in daemons) {
    def trd = daemon.createTestRunDaemon()
    trd.setGlobal('machines', daemons.size().toString())
    def context = trd.createContext()
    contexts << context
    context.runTest(testcase)
}
// Wait for tests to terminate
for (i in 0..<contexts.size()) {
    def context = contexts[i]
    context.waitForRunState(DaemonRunContext.STATE_FINISHED, timeout)
    byte[] runlog = context.getRunLog()
    def fos = new FileOutputStream("$logfile${i + 1}.qrl")
    fos.write(runlog)
    fos.close()
    context.release()
}
Beispiel 16.14:  Groovy-Daemon-Skript CallDaemon.groovy

Zur Ausführung des Groovy-Skripts werden die QF-Test-Bibliotheken qftest.jar, qfshared.jar und qflib.jar benötigt und außerdem die Groovy-Bibiliothek, die auch Bestandteil der QF-Test Installation ist. Das folgende Befehlsskript zeigt, wie das geht:

@echo off
setlocal
set qftestdir=c:\programs\qftest\qftest-3.4.7
set qflibdir=%qftestdir%\qflib
set classpath=%qftestdir%\lib\groovy-all.jar
set classpath=%classpath%;%qflibdir%\qftest.jar;%qflibdir%\qfshared.jar;%qflibdir%\qflib.jar
java -cp %classpath% groovy.ui.GroovyMain CallDaemon
Beispiel 16.15:  Befehlsskript calldaemon.bat zur Ausführung von Calldaemon.groovy

Damit aus dem Daemon-Beispiel ein Lasttest wird (vgl. Kapitel 21), müssen die Testläufe an mindestens einer Stelle innerhalb von "Mein Testfall" synchronisiert werden (z. B. nach dem Starten des SUT). Dazu dient die rc-Methode syncThreads:

def machines = rc.getNum('machines')
rc.syncThreads('startup', 60000, -1, machines)
Beispiel 16.16:  Groovy 'Server Skript' Knoten zur Synchronisation der Testläufe

Die Variable machines bezeichnet die Anzahl der Rechner. Sie wird zum Beispiel im 'Testsuite' Knoten mit einem Vorgabewert von 1 definiert. Bei der Ausführung der Testläufe wird sie mit dem aktuellen Wert überschrieben.