18. August 2021
TestRunListener in QF-Test
Das TestRunListener Interface kann genutzt werden um vor oder nach der Ausführung eines Knotens oder beim Fehlern weitere Aktionen auszuführen.
Diese Aktionen können zum Beispiel der Testdokumentation oder der Fehleranalyse dienen. Im Folgenden werden einige TestRunListeners vorgestellt (Jython Server Skripte):
Variablen überwachen
Folgender TestRunListener loggt immer dann einen Fehler, wenn sich der Wert einer bestimmten Variable (im Bespiel offset) ändert. Dies ist vor allem beim debuggen einer Testsuite hilfreich wenn nicht klar ist wo genau der Variable ein bestimmter Wert zugewiesen wird:
varname = "offset" from de.qfs.apps.qftest.extensions.qftest import AbstractTestRunListener def checkVar(state, var): try: val = rc.lookup(var) if(val != state): state = val rc.logError("Variable '%s': %s" % (var, state)) except: if state != "": rc.logError("Variable '%s' nicht verfügbar" % var) state = "" return state class VarChanger (AbstractTestRunListener): def __init__(self): self.state = None def nodeEntered(self, event): self.state = checkVar(self.state, varname) def nodeExited(self, event): self.state = checkVar(self.state, varname) global varChanger try: rc.removeTestRunListener(varChanger) except: pass varChanger = VarChanger() rc.addTestRunListener(varChanger)
Alternativ kann dieses Skript leicht so angepasst werden, dass der TestRunListener immer dann anschlägt wenn der Variable ein bestimmter Wert zugewiesen wird.
Fehlerton
Es kann vorkommen, dass ein Benutzer mehrere Tests auf mehreren Computern gleichzeitig Tests anwirft. Folgender TestRunListener hilft, wenn es vorkommen kann, dass die Testausführung auf einer Maschine - aus welchen Gründen auch immer - ins stocken geraten kann; Der TestRunListener spielt nämlich immer dann einen Ton ab, wenn die Ausführung eines Knotens länger als ein bestimmtes vorgegebenes Zeitlimit dauert. Hierbei ist es möglich dieses Zeitlimit für einen Knoten mithilfe eines @informUserTimeOut Doctags neu zu definieren. (Hinweis: Die Tonausgabe funktioniert wohl nur auf Windows).
# A TestRunListener that informs the user when the current testrun has stopped for more then a certain period of time. # some imports we need later from de.qfs.apps.qftest.extensions.qftest import AbstractTestRunListener from java.lang import System, Thread from java.awt import Toolkit import time, re # The default time in ms after which to inform # the user. TIME_IN_MS_AFTER_WHICH_TO_INFORM_THE_USER = 2 * 60 * 1000 # The function that is used in order to inform the user def informUserAction(lastActionTimestamp): """ This function is called whenever the test- runlistener detected the timeout. In oder words, this function should play a sound or do something else in order to inform the user. :lastActionTimestamp: The timestamp of the last action. """ print "=== HEY %s ms passed, since we entered the last node! ===" % (System.currentTimeMillis() - lastActionTimestamp) # output a sound on windows Toolkit.getDefaultToolkit().getDesktopProperty("win.sound.exclamation").run() # the thread that will inform you once the timeout is reached ... class InformingThread(Thread): def __init__(self): self.updateTimeout(TIME_IN_MS_AFTER_WHICH_TO_INFORM_THE_USER) self.stop = False def updateTimeout(self, timeout): self.lastAction = System.currentTimeMillis() self.errorAfterTimestamp = self.lastAction + timeout def run(self): while not self.stop: time.sleep(1) if System.currentTimeMillis() > self.errorAfterTimestamp and not self.stop: try: informUserAction(self.lastAction) except: pass # the testrunlistener that keeps track of when a new node get's enetered ... class InformUserWhenHaveBeenIdleTestRunListener(AbstractTestRunListener): def __init__(self): self.thread = InformingThread() self.thread.start() self.myRegex = re.compile("@informUserTimeOut\\s+(\\d+)", re.DOTALL) def nodeEntered(self, event): timeout = 0 comment = event.getNode().comment if comment != None and comment.find("@informUserTimeOut") != -1: match = self.myRegex.search(comment) if match: timeout = int(match.group(1)) self.thread.updateTimeout(TIME_IN_MS_AFTER_WHICH_TO_INFORM_THE_USER if timeout <= 0 else timeout) def runStopped(self, event): self.thread.stop = True # register the testrun listener global informUserWhenHaveBeenIdleTestRunListener try: informUserWhenHaveBeenIdleTestRunListener.thread.stop = True rc.removeTestRunListener(informUserWhenHaveBeenIdleTestRunListener) except: pass informUserWhenHaveBeenIdleTestRunListener = InformUserWhenHaveBeenIdleTestRunListener() rc.addTestRunListener(informUserWhenHaveBeenIdleTestRunListener)
Check-Knoten zählen
Folgender TestRunListener zählt wie viele Check-Knoten während einer Testausführung von QF-Test ausgeführt wurden:
from de.qfs.apps.qftest.extensions.qftest import AbstractTestRunListener class StatisticTestRunner (AbstractTestRunListener): def __init__(self): self.steps, self.checkSteps = 0, 0 def runStopped(self, event): print "steps: %s\ncheckSteps: %s" % (self.steps, self.checkSteps) self.steps, self.checkSteps = 0, 0 # reset counts def nodeEntered(self, event): self.steps += 1 if (event.getNode().getType().startswith("Check")): self.checkSteps += 1 global statisticTestRunner try: rc.removeTestRunListener(statisticTestRunner) except: pass statisticTestRunner = StatisticTestRunner() rc.addTestRunListener(statisticTestRunner)
Generelle Hinweise
- Ähnlich wie in SUT Skripten sollte der Aufruf von rc.callProcedure innerhalb eines TestRunListener vermieden werden. Es ist zwar oft möglich rc.callProcedure innerhalb eines TestRunListener aufzurufen, allerdings kann dies auch zu unvorhergesehenen Problemen führen.
- Anstelle von TestRunListener sollte immer von AbstractTestRunListener abgeleitet werden.