25.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 33) ebenso hilfreich sein wie für die Integration mit vorhandenen Testmanagement- oder Testdurchführungs-Werkzeugen (vgl. Kapitel 28).

HinweisGUI Tests benötigen eine aktive Benutzersession um korrekt ausgeführt werden zu können. Sie finden im Kapitel Aufsetzen von Testsystemen nützliche Tipps und Tricks für die Einrichtung Ihrer Testsysteme. Der technische Hintergrund ist in FAQ 14 beschrieben.

25.2.1
Starten des Daemons

!!! Warnung !!!

Jeder, der Zugriff auf den QF-Test Daemon hat, kann auf dessen Rechner Programme mit den Rechten des Benutzerkontos starten, unter dem der Daemon läuft. Daher sollte Zugriff nur berechtigten Nutzern gewährt werden.

Wenn Sie den Daemon nicht in einer sicheren Umgebung betreiben, in der jeder Nutzer als berechtigt gilt, oder wenn Sie eine eigene Bibliothek zum Zugriff auf den Daemon entwickeln, sollten Sie unbedingt Abschnitt 55.3 lesen. Darin ist beschrieben, wie Sie die Kommunikation mit dem Daemon mittels SSL absichern können.

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 25.9:  Starten des QF-Test Daemon

Hinweis Wichtiger Hinweise zur Kompatibilität:

3.5+ Beginnend mit QF-Test Version 3.5 wird für die Kommunikation mit dem Daemon standardmäßig SSL verwendet. Um mit einer QF-Test Version älter als 3.5 interagieren zu können, muss der Daemon mit leerem Kommandozeilenargument -keystore <Keystore-Datei> in folgender Form gestartet werden:

qftest -batch -keystore= -daemon -daemonport 12345
Beispiel 25.10:  Starten des QF-Test Daemon ohne SSL

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 Daemon.

3.0+25.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
Beispiel 25.11:  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 Daemon 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 25.12:  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 25.13:  Beenden eines QF-Test Daemon

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

25.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 55 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)
    if not context.waitForRunState(DaemonRunContext.STATE_FINISHED, timeout):
        # Run did not finish, terminate it
        context.stopRun()
        if not context.waitForRunState(DaemonRunContext.STATE_FINISHED, 5000):
            # Context is deadlocked
            raise UserException("No reply from daemon RunContext.")
        rc.logError("Daemon call did not terminate and had to be stopped.")
    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 25.14:  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 keystore = "z:\\mysuites\\mydaemon.keystore"
def password = "strengGeheim"

def locator = DaemonLocator.instance()
locator.setKeystore(keystore)
locator.setKeystorePassword(password)
def daemons = locator.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 25.15:  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-Bibliothek, die auch Bestandteil der QF-Test Installation ist. Das folgende Befehlsskript zeigt, wie das geht:

@echo off
setlocal
set qftestdir=c:\programs\qftest\qftest-9.0.0
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 25.16:  Befehlsskript calldaemon.bat zur Ausführung von Calldaemon.groovy

Der DaemonLocator kann beim externen Zugriff den Keystore zur Sicherung der Kommunikation nur automatisch ermitteln, wenn die Datei qftest.jar (wie in diesem Befehlsskript) direkt aus dem QF-Test Verzeichnis geladen wird. Alternativ kann der Keystore wie im Groovy-Skript gezeigt mit setKeystore und setKeystorePassword direkt gesetzt werden, oder indirekt über die System-Properties javax.net.ssl.keyStore und javax.net.ssl.keyStorePassword.

Damit aus dem Daemon-Beispiel ein Lasttest wird (vgl. Kapitel 33), 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 25.17:  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.