23
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 Wurzelverzeichnisses von QF-Test. Bitte beachten Sie, dass Sie veränderte Testsuiten am besten in einem projektspezifischen Ordner speichern.

23.1
Beispiele für Datentreiber

Ein einfacher datengetriebener Test
Abbildung 23.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 23.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 23.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 23.4:  Datengetriebene Tests mit verschachtelten Schleifen

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

Zweites Beispiel einer Datentabelle
Abbildung 23.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 23.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.

23.2
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).

23.3
Beispiele für Datentreiber

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

23.4
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 42.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, Map 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 23.1:  Beispiele für die Verwendung des databinder Moduls