Data Warehouse Test 

 8. April 2021

Alternative Ansätze zum Test eines Datawarehouse-Systems (DWH)

Konventionelles Testen betrieblicher Informationssysteme sieht vor, dass Tester an einem Bildschirmarbeitsplatz sitzen und die Benutzeroberflächen bedienen. Sie versorgen die Eingaben, starten eine Transaktion und prüfen die Ergebnisse. Jede Transaktion ist ein Testfall. Das Gleiche trifft auch für Internetsysteme zu, die transaktionsorientiert sind. Für diese Art Systemtest simuliert der Tester den späteren Endbenutzer und braucht keine höhere Qualifikation als der Endbenutzer selbst.

Mit Batch-Systemen bzw. Hintergrundprozessen war die Testproblematik schon immer eine andere. Man ist hier viel mehr auf Testwerkzeuge angewiesen. Der Tester braucht eine viel höhere Qualifikation, weil der Test größtenteils unsichtbar ist. Es werden eine oder mehrere Eingabedateien bereitgestellt, dann werden Batchläufe gestartet, die jene Dateien verarbeiten, um Datenbanken zu aktualisieren bzw. um Berichte zu generieren. Die Aufgabe des Testers besteht darin, die Eingabedateien und/oder Datenbanken  mit Testdaten zu befüllen, die Jobs zu starten und die Ergebnisse zu validieren.

Dies klingt zunächst einfach, ist aber in der Tat eine sehr schwierige Aufgabe, weitaus schwieriger als der Test einer Benutzeroberfläche. Datawarehouse-Systeme gehören zu dieser Kategorie von IT-Systemen. Beim Test einer Benutzeroberfläche ist die Beziehung zwischen den Eingabedaten und den Ergebnisdaten eindeutig. Sie gehören zur gleichen Transaktion. Außerdem sind sie in der Anzahl überschaubar. Eine Eingabemaske wird ungefähr 10 Datenwerte beinhalten. Die Zahl der Ergebnisse in der Ausgabemaske ist meistens noch kleiner. Also hat der Tester pro Transaktion mit einer überschaubaren Datenmenge zu tun.

Mit einem Batchtest und erst recht mit Datawarehouse-Systemen sieht dies anders aus. Ein Datawarehouse wird in der Regel durch mehrere Quellsysteme mittels Lade-Jobs mit Eingabedateien/Tabellen befüllt. Jedes Eingabemedium hat eine Vielzahl einzelner Attribute, oft mehr als hundert. Bei mehreren Eingabequellen vervielfacht sich dadurch die Anzahl der Datenattribute. Innerhalb des Datawarehouse-Systems arbeiten meist noch viele Jobs an der Transformation und Aggregation dieser Attribute. Der Zusammenhang zwischen den Eingabedaten auf der einen Seite und den im Datawarehouse gespeicherten Daten ist keineswegs so eindeutig, wie beim Test von Benutzeroberflächen und, im Gegensatz dazu, kaum intuitiv nachzuvollziehen.

Datawarehouse Struktur
Datawarehouse Struktur

In dieser Situation ist der Tester auf formale Mittel angewiesen. Er braucht Regeln, die den Zusammenhang zwischen den Eingaben und Ausgaben beschreiben. Anderenfalls kann er nur die Plausibilität der Ergebnisdaten kontrollieren, d.h. ob sie zu einem gültigen Wertebereich gehören, unabhängig von dem Zustand der Eingabedaten. Dies ist allerdings ein sehr oberflächlicher Test. Wenn es heißt, jede Zahl > 0 und < 1000 ist plausibel, dann ist jede Zahl zwischen 0 und 1000 ein korrektes Ergebnis, egal wie sie entstanden ist. Ein derartiger Test ist zwar besser als gar keiner, aber kaum ausreichend, um Korrektheit zu garantieren.

Auch statistisches Testen bzw. das Vergleichen der Häufigkeit ausgewählter Ergebnistypen ist unzulänglich, da es nur grobe Abweichungen zwischen Soll und Ist aufdecken kann. Es ist besser als der Plausibilitätstest, aber immer noch nicht gut genug, um die Korrektheit der Ergebnisse nachzuweisen. Es kann allenfalls einige stellvertretende Abweichungen aufdecken. 1

Testautomatisierung im Data Warehouse Test

Die einzig wirklich effektive Methode zur Verifikation aller Einzelergebnisse ist, jedes Einzelergebnis gegen seine Regel zu prüfen. Für jeden Ergebniswert ist an Hand der vorgeschriebenen Formel seiner Entstehung sowie der Argumente bzw. Eingabewerte nachzurechnen, ob der Wert korrekt ist oder nicht. Im Prinzip lässt sich diese Prüfung manuell durchführen. Der Tester braucht lediglich ein Fenster auf die Eingangsdaten, ein zweites Fenster auf die Regel und ein drittes Fenster auf die Ergebnisse. Auf diese Weise braucht er gut eine halbe Stunde für jeden Ergebniswert. Bei größeren Datawarehouse-Systemen ist es durchaus möglich, dass aus einem Quellsystem an die 5000 Attribute gelesen werden. Auch wenn er nur einen stellvertretenden Ergebniswert für jeden Ergebnistyp verifizieren will, benötigt er bei 5000 Ergebnistypen ca. 2500 Stunden oder 312,5 Tage intensiver Arbeit allein für die Verifikation der Ergebnisse. Bei Betrachtung weiterer Quellsysteme und auch den Transformationen innerhalb des Datawarehouses vervielfacht sich der manuelle Aufwand.

Das andere Problem mit diesem manuellen Abgleich ist, dass er nicht nur zeitaufwändig, sondern auch sehr anspruchsvoll ist. Es erfordert die volle Konzentration des Testers, wenn er die Regel für jeden Ergebnistyp nachvollziehen will. Eine derartig intensive mentale Anstrengung ist von Mitarbeitern unter den heutigen Projektbedingungen kaum zu erwarten. Die Fehlerrate der Regelnachvollziehung wäre höher als die Fehlerrate der Regelkodierung. Darum bleibt nur eine Lösung, nämlich die Ergebnisprüfung zu automatisieren. In einem unserer ersten Datawarehouse-Testprojekte konnten wir 2006 mit der automatisierten Ergebnisprüfung Erfahrungen sammeln.

Spezifikation der Transformationsregeln

Am Anfang steht die Anforderungsspezifikation. Ohne Spezifikation gibt es nach Hutcheson keine Möglichkeit der Verifikation2. In einem Datawarewarehouse-Projekt ist die Spezifikation die Summe aller Datentransformationsregeln. Im vorliegenden Datawarehouse-Projekt wurden die Datentransformationen mit einem Entity-Relationship-Modellierungswerkzeug erstellt, und die Regeln in Excel-Tabellen spezifiziert. In jeder Tabelle gab es eine Zeile für jedes neue Datenattribut bzw. jeden Ergebnistyp. In den Zeilen waren neben Tabellen- und Attributnamen auch der Attributtyp, die Attributbeschreibung und natürlich die Regel für das Attribut dokumentiert. Für das hier beschriebene Beispiel wurden Werte und Regeln der Lade-Jobs aus einem Quellsystem herangezogen. Der Test der weiteren Quellsysteme und der Transformations- und Aggregations-Jobs im Datawarehouse-System erfolgte nach dem gleichen Schema.

Abzug aus der originalen Regeltabelle:

INTEREST_RATE: "Link to TAB_X / ATTR_D interest conditions applicable to the account."; If ATTR_B in TAB_Y(debit) has a value other than 0, this account is linked to an interest group. The following then applies: TAB_Y / KEY (Position 3-4) (Interest Type) A(2), TAB_Y / KEY (Position 5-9) (Interest Subtype) A(5). TAB_Y / KEY (Position 10-12) A(3). The above Key fields are concatenated in the ID. If in TAB_B the ATTR_D values are zeroes, the account interest condition has to be extracted from the X_TAB: TAB_X / ATTR_B (Base Rate) TAB_X / ATTR_S (Spread Rate). If both attributes <> 0 value, extract TAB_X If TAB_X / ATTR_D is available (i.e. <> 0), extract the same as for TAB_X_ID. If only ATTR_D of TAB_Y <> value, extract ATTR_D.

Die Regeln wurden in quasi-formaler englischer Prosa von den Analytikern verfasst. Auf den ersten Blick schien es, als ob eine automatische Umsetzung der informalen Regeln in einen formalen interpretierbaren Ausdruck unmöglich wäre. Bei näherer Betrachtung zeigten sich jedoch gewisse Grundmuster in den Regeln. Über 4700 der 5300 Ergebnisattribute wurden mit einem der folgenden 7 Regelmuster spezifiziert:

  • Regelklasse 1: Eine einfache 1:1 Zuweisung eines Quellattributes.
  • Regelklasse 2: Eine einfache Zuweisung eines konstanten Wertes.
  • Regelklasse 3: Eine Auswahl aus einer Liste alternativer Konstantenwerte (Enumeration).
  • Regelklasse 4: Eine Vereinigung mehrerer Quellattribute aus einer Quelltabelle (Concatenation).
  • Regelklasse 5: Eine Vereinigung mehrerer Quellattribute aus mehreren Quelltabellen (Join).
  • Regelklasse 6: Eine Zuweisung des Zielattributes aufgrund einer Rechenoperation mit konstanten Werten und/oder Quellattributen.
  • Regelklasse 7: Eine Zuweisung der Ergebnisse einer Funktion.

Jede dieser Zuweisungsregeln konnte bedingt oder unbedingt sein. Bedingte Regeln waren mit irgendeiner Bedingung verknüpft, z.B.: if, when, as long as, in case, etc. Die Bedingungsoperanden konnten sowohl konstante Werte als auch Quellattribute sein. Eine besondere Bedingung war das Gelingen einer Vereinigungsoperation (Join). In der Regel handelte es sich um einfache ifs mit else/otherwise Klauseln, die aber nicht immer eindeutig waren.

Natürlich wurden diese Grundregeln durch verschiedene Analytiker immer etwas anders formuliert, aber sie waren ähnlich genug, um sie erkennen und klassifizieren zu können. Außerdem waren die konstanten Werte und die Quellattribute erkennbar. Die Quellattribute wurden immer in Großbuchstaben geschrieben, die konstanten Werte waren unter Hochkomma gesetzt. Dies ergab die Basis zur automatischen Umsetzung dieser semi-formalen Regeln in streng formale, testbare Assertion-Anweisungen, die durch einen Assertion-Compiler in interpretierbare Testprozeduren übersetzt werden konnten. Vorher mussten die Regeln allerdings zum Teil umgeschrieben werden, um analysierbar zu sein. Diese Aufgabe wurde von den Testern durchgeführt. Ca. 3900 Regeln gehörten zu den Regelklassen 1 und 2 und mussten nicht überarbeitet werden. Die übrigen 1400 Regeln mussten umgeschrieben werden, es wurden jedoch auf Grund des Zeitmangels nur 750 Regeln nach folgenden Mustern umgesetzt:

Regelklasse 3:

Die Regeln für eine Liste alternativer Werte (Enumeration) konnten sowohl aus Konstanten als auch aus Variablen bestehen. Die Operanden wurden durch „!“-Zeichen getrennt.

assign ‘const01’ ! ‘const02’ ! ‘const03’
assign TAB_A.ATTR_1 ! TAB_A.ATTR_2 ! TAB_A.ATTR_3

Regelklasse 4:

Die Regeln für eine Vereinigung (Concatenation) mehrerer Quellattribute aus einer Quelltabelle konnten sowohl aus Konstanten als auch aus Variablen bestehen. Die Operanden wurden durch „÷“-Zeichen getrennt.

assign TAB_A.ATTR_1 ÷ ‘_‘ ÷ TAB_A.ATTR_2

Regelklasse 5:

Die Regeln für eine Vereinigung mehrerer Quellattribute aus verschiedenen Quelltabellen wurden als Join-Operationen dargestellt.

assign join TAB_A.ATTR_1 with TAB_B.ATTR_2 with TAB_C.ATTR_3

Regelklasse 6:

Die Regeln für die Zuweisung von Zielattributen durch Rechenoperationen mussten geringfügig umformuliert werden, um eindeutig zu werden.

assign TAB_A.ATTR_1 * -1 + TAB_B.ATTR_2 / 2

Da eine Klammerung nicht vorgesehen war, mussten die Operanden in die richtige Reihenfolge gebracht werden, damit sie von links nach rechts aufgelöst werden konnten. Für die Mehrzahl der arithmetischen Ausdrücke reichte dies aus.

Regelklasse 7:

Die Regeln für die Zuweisung von Funktionsergebnissen wurden formalisiert als ein Funktionsaufruf mit den Parametern in geschweiften Klammern notiert.

assign Func.x {TAB_A.ATTR_1, ’2’, TAB_B.ATTR_2}

Schließlich wurden alle Bedingungen in eine einheitliche Form umgesetzt. Diese Form sah zwecks besserer Verständlichkeit eine nicht geklammerte Auflistung von und/oder-Vergleichen vor.

assign  <Zuweisung> if TAB_B.ATTR_1 = ’1’ and TAB_C.ATTR_2 = ‘0’
assign  <Zuweisung> if TAB_D.ATTR_3 < TAB_E.ATTR_1 or TAB_D.ATTR_3 > ‘0’

Der Ausgang einer Join-Operation konnte auch Bestandteil einer Bedingung sein, wie das folgende Beispiel zeigt:

assign  <Zuweisung> if join TAB_A.ATTR_1 with TAB_B.ATTR_3 = true

Alle if-Anweisungen waren mit einer assign-Zuweisung verbunden. Es wurde zuerst die assign-Zuweisung und danach die bestimmende Bedingung notiert.

assign TAB_D.ATTR_2 if join TAB_D.ATTR_1 with TAB_B.ATTR_1 = true

Das Leitmotiv für diese Syntax war, so nahe wie möglich an der bestehenden englischen Prosaregel zu bleiben, damit die zuständigen Analytiker sie leicht verstehen konnten. Somit stellt sie einen Kompromiss dar, und zwar zwischen der Notwendigkeit, von Fachleuten verstanden zu werden und der Notwendigkeit, von einem Automat interpretiert zu werden. Als solche kommt die Ausdrucksform in die Nähe einer fachspezifischen Sprache für Datawarehouse-Projekte.

Abzug aus der angepassten Regeltabelle:

INTEREST_RATE: "Link to TAB_X.ATTR_D interest conditions.
Debit interest conditions applicable to the account.";
" ? assign TAB_Y.ATTR_D | TAB_Y.ATTR_C | TAB_X.ATTR_C | 'D' if TAB_Y.ATTR_D (debit) <> '0',
assign TAB_X.ATTR_C if TAB_X.ATTR_D <> '0',
assign TAB_X./ATTR_C | TAB_X.ATTR_D if TAB_X.ATTR_B <> '0',
assign 'na' if TAB_Y.ATTR_D = '00' and TAB_X.ATTR_S = '0' and TAB_X.ATTR_B = '00'
assign TAB_X.ATTR_N|ATTR_Y|ATTR_O|ATTR_A if other.(comment).";

Im Zuge der formalen Überarbeitung der Regeln wurden Dokumentationsfehler entdeckt, welche von den Testern an die Analytiker gemeldet wurden. Hierbei handelte es sich meist um unvollständige Regelbeschreibungen (if ohne else, fehlende Schlüssel bei Join-Anweisungen, etc.) die Raum für Interpretationen gelassen haben, was eine exakte Überprüfung der Regel verhinderte. Dies betraf rund 4% aller Regeln.

Umsetzung der Regeln in Testskripte

Von den rund 5300 Regeln in der Spezifikation konnten über 3900 ohne Anpassung umgesetzt werden. Somit waren 1400 Regeln anzupassen, wobei sich ca. 600 als nicht umsetzbar herausstellten. Am Ende wurden knapp 800 Regeln in die formale Syntax umgeschrieben. Dies dauerte im Durchschnitt 20 Minuten pro Regel. Also waren rund 266 Stunden erforderlich, um die Regeln zu formalisieren. Alternativ dazu hätte es die Möglichkeiten gegeben a) diese Regeln nicht zu testen oder b) die Assertion-Skripte manuell zu verfassen.

Das manuelle Verfassen der Assertion-Skripte hätte jedoch genau so viel, wenn nicht noch mehr Aufwand gekostet. Außerdem hätte man dann zwei semantische Ebenen mit zwei verschiedenen Beschreibungen ein und derselben Regel fortschreiben müssen. Ein ähnliches Problem taucht bei der Codeerstellung auf. Wird der Code aus der Spezifikation generiert, muss nur die Spezifikation gepflegt werden. Wenn aber, was meistens der Fall ist, der Code manuell geschrieben wird, muss sowohl die Spezifikation als auch der Code fortgeschrieben werden. Da dies auf Dauer zu aufwendig ist, wird nach kurzer Zeit die Spezifikation vernachlässigt und nur noch der Code weiterentwickelt. Damit sind die Analytiker aus dem Spiel und das Produkt bleibt in den Händen der Programmierer mit allen negativen Folgen, die mit historisch gewachsenen Systemen verbunden sind, vor allem, dass keiner sie versteht, nicht einmal der Programmierer selbst.

Um diesen Zustand zu vermeiden empfiehlt es sich, nur eine Beschreibung eines IT-Systems zu pflegen und zwar jene, deren Sprache der menschlichen Sprache am nächsten kommt – die fachspezifische Sprache. Zumindest die Testskripte sollten automatisch generiert werden. Dies ist auch in diesem Projekt geschehen. Die Assertions wurden aus den spezifizierten Regeln mit einem Tool erzeugt.

Generiertes Testskript:

file: ACCOUNT;
// This comparison procedure assumes that the old file contains the following attributes:
//      from TAB_X Table: A_ID, A_TYPE, ATTR_S, ATTR_R, ATTR_C, ATTR_S, ATTR_D, ATTR_F, ATTR_N
//      from TAB_Y Table: ATTR_C, ATTR_D, ATTR_E, ATTR_F, ATTR_P
//      from TAB_Z Table: ATTR_R
if ( new.A_ID = old.ATTR_ID );
assert new.A_ID = old.A_TYPE if (old.A_TYPE = "G");
assert new.A_ID = "S" if (old.ATTR_A = "R" & old.ATTR_S = "S");
assert new.A_ID = "C" if (old.ATTR_A = "R" & old.ATTR_S = "C");
assert new.A_TYPE = "L" if (old.ATTR_A = "R" & old.ATTR_S = "L");
assert new.A_RATE = old.ATTR_C if (old.ATTR_B = "0");
assert new.A_INTEREST = old.ATTR_D;
assert new.A_LIQU = old.ATTR_E;
assert new.A_ACCT = old.ATTR_P;
assert new.A_PC   = old.ATTR_PC;
assert new.START_DATE = "2005.05.15" if (old.ATTR_H <> "0");
assert new.REVIEW_DATE = old.ATTR_V if (old.ATTR_F <> "0" & old.ATTR_N <> "0") ;
assert new.REVIEW_DATE = "NULL" if (old.ATTR_F = "0") ;
assert new.PRIMARY_INDICATOR = "P" ! "S" ! "n.a.";
assert new.INDICATOR = "inactiv" if (old.ATTR_R = X"3") ;
assert new.INDICATOR = "inactiv" if (old.ATTR_R = X"1") ;
assert new.INDICATOR = "closed" if (old.ATTR_R = "C") ;assert new.INDICATOR = "active" if (other);
end;

Das Einfache an Test-Assertions gegenüber Codes ist, dass sie für eine bestimme Datei oder Tabelle in einer beliebigen Reihenfolge geordnet werden können. Jedes Attribut einer Tabelle wird allein für sich, unabhängig von den anderen Zielattributen geprüft. Wichtig ist nur, dass alle Assertions einer Tabelle zusammen in einer Testprozedur sind. Die Testprozedur hat den Namen der Zieltabelle und eine if-Key-Bedingung, um das Zielobjekt mit dem Quellobjekt zu verbinden. Diese if-Key-Bedingung hat folgendes Format:

if     (TAB_X.KEY_1 = TAB_A.KEY_1 &   TAB_X.KEY_2 = TAB_B.KEY_2 &   TAB_X.KEY_3 = TAB_C.KEY_3 &

Der Schlüsselvergleich sucht den Abgleich des Zielobjektes mit den richtigen Quellobjekten. Die Daten der Quellobjekte werden vorher per select- und join-Anweisungen aus den Quelltabellen geholt und zu Vergleichsobjekten entsprechend den Zielobjekten zusammengefasst. D.h. für jedes Zielobjekt gibt es ein Vergleichsobjekt mit allen Quelldaten, aus denen das Zielobjekt gebildet wird. Diese Erstellung der Vergleichsobjekte aus den Ausgangstabellen ist eine Voraussetzung für die Verifikation der Ergebniswerte. Alle Vergleichsobjekte derselben Objektart bzw. alle Vergleichsobjekte für eine Zielobjektart, werden als Zeilen in einer Tabelle zusammengefasst.

Deshalb wird als Teil der Assertion-Generierung auch die select-Anweisung für die Bildung der Vergleichsobjekte generiert. Diese select-Anweisungen gehen aus den join- und select-Anweisungen der Regelspezifikation hervor.

Die Assertion-Anweisungen selbst sind bedingt oder unbedingt. Bedingte Assertions sind mit einer if Bedingung verknüpft.

Die Assertion-Anweisung hat folgende Grundsyntax:

assert new.TAB.ATTRIBUT <oper> <Vergleichsausdruck>;

Der Operator <oper> kann =, <, >, <=, =>, <> oder != sein.

Der Vergleichsausdruck kann einer der folgenden sein:

  • <operand> im Falle einer 1:1 Zuweisung.
  • <operand> ! <operand> ! <operand> im Falle einer Liste alternativer Werte (Enumeration).
  • <operand> | <operand> | <operand> im Falle eines vereinigten Vergleichswertes (Concatenation).
  • Range <grenzwert>’:’<grenzwert>’ im Falle einer Grenzwertprüfung.
  • <operand> <arithmetischer Ausdruck> im Falle einer Rechenoperation.

Der Operand ist eine Konstante wie “const01“ oder ein Quellattribut wie TAB_A.ATTR_1.

Falls die Assertion bedingt ist, wird eine if-Bedingung angehängt:

<assertion><condition>

Die Bedingung <condition> hat folgende Syntax:

if (<quellattribut><oper><operand> {&
<quellattribut><oper><operand>});

Das Quellattribut ist ein Attribut in den Quelltabellen z.B. TAB_A.ATTR_1. Die Operanden sind dieselben wie in der assert-Zuweisung.

Es können mehrere ‘und’-Bedingungen mit einer & Verbindung verknüpft werden. ‚oder’-Bedingungen werden als alternative Assertions für das gleiche Zielattribut erfasst.

assert new.TABX.ATTR_1=’1’ if (TAB_A.ATTR_2=’A’);
assert new.TABX.ATTR_1=’2’ if (TAB_A.ATTR_2=’B’);
assert new.TABX.ATTR_1=’3’ if (TAB_A.ATTR_2=’C’);

Regeln der Regelklasse 7 (Funktionen) konnten durch den Assertion-Generator nicht in prüfbare Assertions umgewandelt werden, da hierzu die Anbindung an die nötigen Funktionsbibliotheken fehlte. Diese Zuweisungen wurden in den Assertion-Prozeduren als Kommentar gekennzeichnet und mussten von den Testern manuell getestet werden.

Der Assertion-Generator hat die Excel-Tabellen, in denen die Regeln spezifiziert sind, geparst und die Regeln der Reihe nach in Assertions umgesetzt. Aus jeder Regel wurden eine oder mehrere Assertions erzeugt, dies erfolgte für die 266 Zieltabellen in weniger als einer Minute. Am Ende gab es 266 Assertion-Prozeduren und ein Protokoll aller fehlenden und nicht übersetzbaren Regeln, eine Statistik über die Regeln sowie SQL-select-Anweisungen für die Auswahl der Quelldaten zu einem Zielobjekt. Der größte Vorteil der automatischen Assertion-Generierung, abgesehen von der syntaktischen Korrektheit der Assertions, ist die Wiederholbarkeit des Vorgangs. Bei einer Änderung der Regeln ist es lediglich erforderlich, die Assertion-Generierung erneut zu starten und so die betroffenen Assertion-Prozeduren neu zu erzeugen.

Statistik aus der Testskriptgenerierung:

+---------------------------------------------------+
| Assertion Generation completed !
| Number of E/R Rule Lines processed       = 11384
| Number of new Tables processed           = 266
| Number of new Attributes processed       = 10910
| Number of old Tables  processed          = 132
| Number of old Attributes processed       = 00891
| Number of new Attributes without a rule  = 05573
| Number of Transformation Rules specified = 05337
| Number of Transformation Rules processed = 04742
| Number of Basel-II Rules recognized      = 00890
| Number of Basel-II Rules processed       = 00852
| Number of Complex  Rules recognized      = 00853
| Number of Complex  Rules processed       = 00562
| Number of Complex Basel Rules recognized = 00157
+---------------------------------------------------+
| Number of Assert Scripts generated       = 00266
| Number of Assertions generated           = 04897
| Number of Table Selectons generated      = 00181
| Number of Assert Keys generated          = 00023
| Number of Assert Conditions generated    = 00308
| Number of Assert Concatenates generated  = 00103
| Number of Assert Alternates generated    = 00365
| Number of Assert Arithmetic generated    = 00009
| Number of Test Cases generated           = 05337
+---------------------------------------------------+

Eine Möglichkeit zur Ermittlung der Testabdeckung ist die Bestimmung des Verhältnisses der durchgeführten Testfälle zu den geplanten Testfällen. Um im vorliegenden Projekt die Testabdeckung messen zu können und auch den Testfortschritt quantifizierbar zu machen, musste für jede zu testende Regel ein eigener Testfall erstellt werden. Da die manuelle Erstellung der Testfälle mit großem Aufwand verbunden gewesen wäre, wurden diese ebenfalls vom Assertion-Generator in Form von CSV-Dateien erzeugt. Die CSV-Dateien konnten so später komplett in das Testcasemanagement-Werkzeug importiert werden.

Für jeden Testfall wurden unter anderem folgende Informationen generiert:

  • Die über alle Regeln eindeutige Testfallnummer.
  • Die Testfallbeschreibung.
  • Das erwartete Ergebnis, welches aus dem Inhalt der Regel bestand.
  • Die Beschreibung der Vor- und Nachbedingungen.
  • Die Priorität des Testfalls. Diese wurde vom Assertiongenerator anhand der Komplexität der Regel ermittelt, wobei Regeln der Klassen 1 und 2 als “Low”, der Klassen 3 und 4 als “Medium” und der Klassen 5,6 und 7 als “High” eingestuft wurden.
  • Div. Metadaten wie z.B.: Testfallautor, Tester, Testsystem, Schlüsselwörter, Testfallstatus.
  • Die Testfallgruppe. Um die Anzahl der Testfälle überschaubar zu halten, wurden sie zu Gruppen zusammengefasst, wobei eine Gruppierung nach Zieltabellen durchgeführt wurde.

Im vorliegenden Datawarehouse-Projekt wurde “Quality Center” der Firma Mercury als Testcasemanagement-Werkzeug verwendet, welches aufgrund der flexiblen Import-Schnittstelle für die genierten Testfälle gut geeignet war.

Der Datawarehouse-Testprozess

Für die mehrfache Wiederholung des Datawarehouse-Tests wurde unter Verwendung der hier beschriebenen Testwerkzeuge ein definierter und wiederholbarer Testprozess festgelegt. Dieser Prozess bestand aus 8 Schritten:

  1. Überarbeitung der Datentransformationsregel in der ursprünglichen Excel-Tabelle.
  2. Generierung der Assertion-Prozeduren aus den Transformationsregeln.
  3. Generierung der Testdaten und der SQL-select-Anweisungen aus den Assertion-Prozeduren.
  4. Kompilierung der Assertion-Prozeduren.
  5. Auslesen und Speichern der Quelltabellen (Quellsystem) in CSV-Dateien.
  6. Laden der Datawarehouse-Datentabellen.
  7. Auslesen und Speichern der Zieltabellen (Datawarehouse) in CSV-Dateien.
  8. Automatisierter Vergleich der CSV-Dateien der Quell- und Zieltabellen anhand der Assertions.

Das Ergebnis der 8 Schritte war ein Protokoll der fehlenden Datenobjekte und der abweichenden Datenattribute, d.h. jener Daten deren Werte nicht mit dem Ergebnis der Assertions übereinstimmten. Diese wurden als inkorrekt ausgewiesen.

Data Warehouse Test Prozess
Datawarehouse Testprozess

Verifikation der Testergebnisse gegen die Assertions

Funktionales Testen setzt ein Orakel voraus. Das Orakel dient als Quelle der Wahrheit, es teilt uns mit, ob die Testergebnisse richtig oder falsch sind. James Bach schreibt: „An oracle is an evaluation tool that will tell you whether the system has passed or failed a test. In high volume automated testing, the oracle is probably another program that generates or checks the test results. The oracle is generally more trusted than the software under test, so a concern flagged by the oracle is worth spending time and effort to check…” 3

Im Datawarehouse-Projekt wurde als Orakel das eigenentwickelte Tool “DataTest” verwendet, das die Assertion-Prozeduren interpretiert und die Testergebnisse gegen die Regel verifiziert hat. Für jede Zieltabelle wurden die entsprechenden Quelldaten gesammelt und in eine Zwischendatenbank gespeichert, wo sie nach deren Schlüssel geordnet wurden. Anschließend wurde die Zieltabelle gelesen und pro Datensatz an Hand der zu diesem Datensatz passenden Quelldaten verifiziert. Dabei wurde jedes Attribut gegen seine Assertions geprüft. Ergebnisdaten, die laut Assertion und Quelldaten vom spezifizierten Ergebnis abwichen, wurden in einem Abweichungsprotokoll aufgelistet. Die durchgeführten Prüfungen haben gezeigt, dass es durchaus möglich ist, eine große Menge an Testergebnissen automatisch in wenigen Minuten zu validieren.

Des Weiteren wurden alle Zieldatensätze, zu denen es keinen passenden Quelldatensatz gab, und Quelldatensätze, zu denen es keinen passenden Zieldatensatz gab, protokolliert. Damit hatten die Tester eine Liste aller ungepaarten Schlüssel und aller fehlerhaft gefüllten Zielattribute des Datawarehouse. Diese mussten natürlich von den Testern manuell überprüft werden, um festzustellen, ob es sich um einen echten Fehler handelte. Das Ergebnis wurde pro Datensatz beim zugehörigen Testfall im Testcasemanagement-Werkzeug „Quality Center“ dokumentiert und, falls der Testfall negativ durchgeführt wurde, ein Fehler eingemeldet. Somit konnte der Testfortschritt für das Controlling und Reporting ermittelt werden.

Auf diese Weise konnten etliche falsche Zuweisungen von den Importdateien zu den Datawarehouse-Tabellen aufgedeckt werden. Leider war es nicht möglich alle Attribute zu verifizieren, da nicht bei allen Regeln eine Umsetzung in Assertions möglich war. Von den 5300 Regeln konnten lediglich 4700 in Assertions transformiert werden. Die Übrigen mussten manuell überprüft werden, was natürlich sehr viel Zeit in Anspruch nahm.

Ausschnitt aus dem Datenvalidierungsbericht:

+----------------------------------------------------------------------------------------+
|                              File/Table Comparison Report                              |
| Key Fields of Record(new,old)                                                          |
+----------------------------------------------------------------------------------------+
| New: ACCOUNT                                                                           |
| Old: Attributes                                                                        |
+----------------------------------------------+-----------------------------------------+
| RecKey:100000USD114601001                    | duplicate key in old File/Table         |
+----------------------------------------------+-----------------------------------------+
| RecKey:100000XXX104501001                    |                                         |
| New: ATTR_ID                                 | G                                       |
| Old: Constant_Value                          | L                                       |
+----------------------------------------------+-----------------------------------------+
| RecKey:100000YYY104501001                    |                                         |
| New: ATTR_C                                  | XXX00                                   |
| Old: ATTR_C                                  | 0                                       |
+----------------------------------------------+-----------------------------------------+
| RecKey:100000ZZZ104501001                    |                                         |
| New: ATTR_D                                 | 0                                       |
| Old: ATTR_P                                  | 1                                       |
+----------------------------------------------+-----------------------------------------+
| RecKey:100000ZZZ104501001                    |                                         |
| New: REVIEW_DATE                             | 9999-08-01_00:00:00                     |
| Old: Constant_Value                          | NULL                                    |
+----------------------------------------------+-----------------------------------------+
| RecKey:100000YYY104501001                    |                                         |
| New: ATTR_P                                  | GL                                      |
| Old: Constant_Value                          | RE                                      |
+----------------------------------------------+-----------------------------------------+
| RecKey:100000XXX104601001                    |                                         |
| New: START_DATE                              | 9999-08-01_00:00:00                     |
| Old: Constant_Value                          | NULL                                    |
+----------------------------------------------+-----------------------------------------+
| RecKey:100000XXX104701001                    |                                         |
| New: ATTR_X                                  | G                                       |
| Old: Constant_Value                          | C                                       |
+----------------------------------------------+-----------------------------------------+
| RecKey:100000ATS196501100                    | missing from the new File/Table         |
+----------------------------------------------+-----------------------------------------+
|  Total Number of old Records checked:                91                                |
|  Number of old Records found in new File:            08                                |
|  Number of old Records with duplicate Keys:          72                                |
|  Number of old Records not in new Table:             11                                |
|  Total Number of new Records checked:                59                                |
|  Number of new Records found in old File:            08                                |
|  Number of new Records with alternate Keys:          00                                |
|  Number of new Records not in old File:              51                                |
|  Total Number of Fields checked:                     93                                |
|  Total Number of non-Matching Fields:                46                                |
|  Percentage of matching Fields:                      51 %                              |
|  Percentage of matching Records:                     14 %                              |
+----------------------------------------------------------------------------------------+

Weitere automatisierte Testdurchführungen

Zusätzlich zu der Verifikation der Transformationsregeln wurden noch weitere Testszenarien im Datawarehouse-Projekt automatisiert. Diese wurden durchgehend in der Scriptsprache Python entwickelt und so entstand im Laufe des Datawarehouse-Projektes ein umfangreiches Framework zur datenbasierten Testautomatisierung und zur statischen Codeanalyse der Datawarehouse-Umgebung. Unter anderem wurden folgende Testansätze in diesem Framework realisiert:

  • Quantitätsprüfungen: Wird die korrekte Anzahl an Datensätzen zwischen den einzelnen Stufen transportiert?
  • Standarddaten: Werden alle Standardwerte korrekt verwendet?
  • Dateninhalte: Stimmen alle Dateninhalte mit den vorgegebenen und erwarteten Datentypen, Datenlängen, etc. überein?
  • Codemerkmale: Werden bei den einzelnen Modulen die korrekten Eigenschaften und Kennzeichen verwendet?
  • Integrity-Check: Ist die Integrität der geladenen Daten gegeben?

Durch die Automatisierung war es auch hier möglich, den Test zum einen wiederholbar zu machen, zum anderen über das komplette Datawarehouse erst möglich zu machen.

Fazit aus dem Datawarehouse-Projekt

Aus dem Datawarehouse-Testprojekt konnten am Ende folgende Schlüsse gezogen werden:

  • Der Schlüssel zur Verifikation des Inhaltes eines Datawarehouses liegt in einer präzisen und vollständigen Spezifikation der Datentransformationsregeln. Diese ist die Basis für die Kontrolle der einzelnen Attribute. Ohne eine derartige Spezifikation ist ein effizienter Test nicht möglich.
  • Bei einem Massentest dieser Art muss es möglich sein, die Transformationsregeln automatisch umzusetzen. Ansonsten wird der Aufwand viel zu hoch, d.h. es muss ein Tool geben, welches aus den Regeln Assertions generiert.
  • Die Assertions müssen maschinell verarbeitbar sein. Es genügt nicht, durch die Datenattribute zu scannen, ob ihre Inhalte plausibel erscheinen. Jedes Attribut muss automatisch gegen seine Regel abgeglichen werden, um seine Richtigkeit feststellen zu können.
  • Ohne mächtige Testwerkzeuge ist der systematische Test eines Datawarehouse-Systems nicht möglich, zusätzlich müssen die Werkzeuge in einen wohldurchdachten Testprozess eingebettet sein. Prozess und Tools müssen sich gegenseitig ergänzen.
  • Ein solch komplexes Testprojekte setzt ein eigenes Testautomatisationsprojekt nach Kaner voraus4

Dieser hier beschriebenen Testansatz war Grundlage für weitere Datawarehouse-Tests und wurde in diesem Zuge immer weiter optimiert.

  1. Dyer, Michael: “Statistical Testing” in The Cleanroom Approach to Quality Software Development, John Wiley & Son, New York, 1992, p. 123
  2. Hutcheson, Maggie.: Software Testing Fundamentals, John Wiley & Sons, Indianapolis, 2003, p. 12
  3. Bach, James: “Reframing Requirements Analysis”, in IEEE Computer, Vol. 32, No. 6, 2000, p. 113
  4. Kaner, C. / Bach, J. / Pettichord, B.: Lessons learned in Software Testing, John Wiley & Sons, New York, 2002, p. 111