Datengetriebenes Testen

Datengetriebenes Testen ist ein sehr wichtiger Aspekt der Testautomatisierung. Das Ziel besteht, kurz gesagt, darin, einen Testfall mehrfach mit dem selben Ablauf, aber unterschiedlichen Eingabe- und Vergleichswerten durchzuführen. QF-Test bietet verschiedene Möglichkeiten, um Daten für datengetriebene Tests abzulegen oder aus externen Quellen zu laden. Die bequemste Variante basiert auf einem 'Datentreiber' Knoten, der die Umgebung zur Iteration über die Datensätze bereitstellt, sowie ein oder mehrere 'Daten' Knoten, welche die Variablen Werte für die Testdurchläufe liefern. Dabei gibt es in QF-Test keinen 'Daten' Knoten als solches. Er dient als Oberbegriff für spezielle Ausprägungen wie eine 'Datentabelle' oder eine 'CSV-Datei'. Das Ganze lässt sich am besten anhand von Beispielen erläutern. Eine Demo-Testsuite mit einfachen und komplexeren Beispielen finden Sie unter dem Namen datadriver.qft im Verzeichnis doc/tutorial unterhalb des Wurzelverzeichnises von QF-Test. Bitte beachten Sie, dass Sie veränderte Testsuiten am besten in einem projektspezifischen Ordner speichern.

Beispiele für 'Datentreiber'

Ein einfacher datengetriebener Test
Abbildung 21.1:  Ein einfacher datengetriebener Test

Obige Abbildung zeigt einen 'Testfallsatz' mit einem 'Datentreiber' Knoten, der einen einzelnen 'Daten' Knoten in Form einer 'Datentabelle' enthält. Der Inhalt des 'Datentabelle' Knotens ist wie folgt:

Beispiel einer Datentabelle
Abbildung 21.2:  Beispiel einer 'Datentabelle'

Wird der 'Testfallsatz' ausgeführt, iteriert er über die Zeilen der 'Datentabelle'. Für jeden der drei Iterationsschritte werden die Werte der entsprechenden Zeile der Tabelle an die Variablen mit dem Namen der jeweiligen Spalte gebunden. Im Beispiel ergibt das für den ersten Durchlauf die Bindungen "Model=Rolo", "Variant=None" und "Price=19000". Im zweiten Durchlauf ist dann "Model=I5", im dritten "Model=Minigolf". Bei jedem Durchgang werden alle 'Testfall' Childknoten des 'Testfallsatz' Knotens ausgeführt.

Die folgende Abbildung zeigt ein Protokoll für obigen 'Testfallsatz':

Protokoll eines datengetriebenen Tests
Abbildung 21.3:  Protokoll eines datengetriebenen Tests

Im nächsten Beispiel sehen wir, dass datengetriebene Tests nicht auf eine einfache Schleife beschränkt sind:

Datengetriebene Tests mit verschachtelten Schleifen
Abbildung 21.4:  Datengetriebene Tests mit verschachtelten Schleifen

Der 'Datentreiber' enthält nun eine zweite 'Datentabelle' mit folgendem Inhalt:

Zweites Beispiel einer Datentabelle
Abbildung 21.5:  Zweites Beispiel einer 'Datentabelle'

Der 'Testfallsatz' wird nun insgesamt sechs Iterationen durchlaufen, da für jede der drei Iterationen der äußeren Schleife namens "cars" beide Iterationen der inneren Schleife namens "accessories" durchlaufen werden. Im folgenden Protokoll ist dies gut zu erkennen:

Protokoll eines datengetriebenen Tests mit verschachtelten Schleifen
Abbildung 21.6:  Protokoll eines datengetriebenen Tests mit verschachtelten Schleifen

Hinweis Die äußerst hilfreichen dynamisch generierten Namen der Schleifendurchgänge erhalten Sie, indem Sie das Attribut 'Name für Schleifendurchgang im Protokoll' des 'Datentreiber' Knotens auf den Wert "car Model: $(Model)" im ersten bzw. "car Model: $(Model), accessory Name: $(Accessory)" im zweiten Beispiel setzen. Wie Sie sehen, wird dieser Wert für jede Iteration individuell expandiert, so dass Sie auf die Variablen zugreifen können, die für die Iteration gebunden werden.

Anwendung von 'Datentreibern'

Wie im vorangehenden Beispiel erklärt, muss ein 'Datentreiber' Knoten in einen 'Testfallsatz' Knoten eingefügt werden, und zwar zwischen die optionalen 'Abhängigkeit' und 'Vorbereitung' Knoten. Wird der 'Testfallsatz' ausgeführt, führt er zunächst einen eventuell vorhandenen 'Datentreiber' Knoten aus. Der Inhalt des 'Datentreiber' Knotens ist nicht auf 'Datentabelle' und 'CSV-Datei' beschränkt. Wie ein normaler 'Sequenz' Knoten kann der 'Datentreiber' jede Art von ausführbaren Knoten enthalten, um damit eventuelle Vorbereitungen durchzuführen, die zur Bereitstellung der Daten notwendig sind. Hierdurch ist es auch möglich, die 'Daten' Knoten mehrfach zu nutzen, indem diese einfach in eine 'Prozedur' gestellt werden, welche aus den 'Datentreiber' Knoten heraus aufgerufen wird.

Im Prinzip entspricht ein 'Daten' Knoten einer Schleife, bei der in jedem Durchgang verschiedene Werte an Variablen gebunden werden. Ein 'Daten' Knoten muss mit einem Namen im 'Datentreiber' Kontext eines 'Testfallsatzes' registriert werden. Dadurch kann die Schleife durch einen 'Break' Knoten mit dem gleichen Namen abgebrochen werden. Nachdem der 'Datentreiber' ausgeführt wurde, iteriert der 'Testfallsatz' über die dabei registrierten 'Daten' Schleifen.

Bei verschachtelten Schleifen wird der zuerst registrierte 'Daten' Knoten als äußerste Schleife ausgeführt. Seine Variablen werden zuerst gebunden und haben damit geringere Bindungskraft als die Variablen der inneren Schleife(n).

Beispiele für 'Datentreiber'

In der mitgelieferten Testsuite doc/tutorial/datadriver.qft finden Sie Beispiele für die Verwendung von CSV- und Excel-Dateien.

Fortgeschrittene Anwendung

Neben dem 'Datentabelle' Knoten gibt es verschiedene weitere Möglichkeiten, Daten in einem Datentreiber zu binden. Die Knoten 'Excel-Datei', 'CSV-Datei', 'Datenbank' und 'Datenschleife' werden alle ausführlich in Abschnitt 40.4 erläutert.

Außerdem können Daten durch Aufruf der 'Prozeduren' qfs.databinder.bindList oder qfs.databinder.bindSets in der Standardbibliothek qfs.qft gebunden werden. Als Parameter werden Listen oder Sätze von Werten in Form von Texten übergeben, die aufgeteilt werden, um über die Werte zu iterieren. Informationen zur Standardbibliothek finden Sie in Kapitel 8 des Tutorials.

Und schließlich können Daten aus Jython (und analog aus Groovy bzw. JavaScript) auch direkt mit Hilfe des databinder Moduls gebunden werden, welches folgende Methoden bietet:

 
 
void bindDict(Object rc, String loopname, dictionary dict, String counter=None, String intervals=None)
Erzeugt und registriert ein databinder Objekt, das Daten aus einem Dictionary bindet. Die Schlüssel definieren die Namen der Variablen und die Werte sind Sequenzen, deren Inhalt an die jeweilige Variable gebunden wird.
Parameter
rcDer aktuelle Runcontext.
loopname Der Name unter dem die Daten gebunden werden, entsprechend dem Attribut 'Name' eines 'Daten' Knotens.
dictDas zu bindende Dictionary.
counter Ein optionaler Variablenname für den Iterationszähler.
intervals Optionale Bereiche von Indizes, getrennt durch Komma, z.B. "0,2-3".
 
void bindList(Object rc, String loopname, String varname, Object values, String separator=None, String counter=None, String intervals=None)
Erzeugt und registriert ein databinder Objekt, das eine Liste von Werten an eine Variable bindet.
Parameter
rcDer aktuelle Runcontext.
loopname Der Name unter dem die Daten gebunden werden, entsprechend dem Attribut 'Name' eines 'Daten' Knotens.
varnameDer Name der zu bindenden Variable.
values Die zu bindenden Werte. Entweder eine Sequenz oder ein Text, der aufgeteilt wird.
separator Optionales Trennzeichen für die Aufteilung der Werte, falls sie als Text übergeben werden. Standard ist Leerraum.
counter Ein optionaler Variablenname für den Iterationszähler.
intervals Optionale Bereiche von Indizes, getrennt durch Komma, z.B. "0,2-3".
 
void bindSets(Object rc, String loopname, Object varnames, Object values, String separator=None, String counter=None, String intervals=None)
Erzeugt und registriert ein databinder Objekt, das Sätze von Werten an einen Satz von Variablen bindet.
Parameter
rcDer aktuelle Runcontext.
loopname Der Name unter dem die Daten gebunden werden, entsprechend dem Attribut 'Name' eines 'Daten' Knotens.
varnames Die Namen der zu bindenden Variablen. Entweder eine Sequenz, oder ein Text, der aufgeteilt wird.
values Die Sätze von zu bindenden Werten. Entweder eine Sequenz von Sequenzen, wobei jede innere Sequenz einem Satz von Daten entspricht, oder ein Text der aufgeteilt wird.
separator Optionales Trennzeichen für die Aufteilung der Variablen und der Sätze von Werten, falls diese als Text übergeben werden. Standard ist Leerraum. Sätze von Werten werden jeweils durch Zeilenumbrüche getrennt.
counter Ein optionaler Variablenname für den Iterationszähler.
intervals Optionale Bereiche von Indizes, getrennt durch Komma, z.B. "0,2-3".
 
 

Einige Beispiele:

import databinder

# Three iterations with the values "spam", "bacon" and "eggs"
# bound to the variable named "ingredient"
databinder.bindList(rc, "meal", "ingredient", ["spam", "bacon", "eggs"])
# Same with string values
databinder.bindList(rc, "meal", "ingredient", "spam bacon eggs")
# Same with string values and special separator
databinder.bindList(rc, "meal", "ingredient", "spam|bacon|eggs", "|")

# Two iterations, the first with item="apple" and number="5",
# the second with item="orange" and number="3"
databinder.bindSets(rc, "fruit", ["item", "number"],
                    [["apple",5], ["orange",3]])
# Same with string values, note the linebreak
databinder.bindSets(rc, "fruit", "item number", """apple 5
orange 3""")

# Same as before with the data stored in a dict
databinder.bindDict(rc, "fruit",
                    {"item": ["apple", "orange"],
                     "number": [5,3]})
Beispiel 21.1:  Beispiele für die Verwendung des databinder Moduls