Montag, 29. Juni 2009

Wie isoliert kann ein Unit Test sein?

Das Wesen des Unit Tests ist, dass er ein kleines Stück Software unabhängig vom "Rest der Welt" testet. Ist diese Unabhängigkeit total? Sicher nicht: Damit er überhaupt ausgeführt werden kann, muss die Laufzeitumgebung vorausgesetzt werden. Dazu gehört in ABAP zum Beispiel auch die Erkennung der DDIC-Typen, die im zu testenden Programm deklariert sind. Im Gegensatz zu Unit Tests in anderen Systemen setzen ABAP Unit Tests eine Datenbankverbindung voraus, von der sie nicht abstrahieren können: Die Defaultverbindung. Denn die Selektionen auf die Tabellen des Data Dictionary wie etwa DD01L können nicht abgeschaltet werden.

Grundsätzlich kann auch der in den Konstrukten der Programmiersprache implizit enthaltene Code nicht abgeschaltet werden. Die Wahrscheinlichkeit, dass dort etwas im Argen liegt, ist bei einer derart ausgereiften Programmiersprache wie ABAP ja auch sehr klein. Dieser Teil der Ausführungslogik gehört gewissermassen zur Infrastruktur, die vorausgesetzt werden kann - und muss.

Wo aber zieht man hier die Grenze? In dem Buch Unit Tests mit ABAP Unit von Damir Majer [1] werden ganz unbefangen auch Anwendungsdaten selektiert. Einige Tests, deren Code er dort zeigt, sind eher als Integrationstests denn als Unit Tests zu bezeichnen, auch wenn sie mit dem ABAP Unit Framework ausgeführt werden. Das Thema Stubs und Mocks wird nur am Rande erwähnt, hier hofft der Autor auf eine Unterstützung durch die SAP-Basisentwicklung in zukünftigen Releases.

Die gundsätzliche Frage bleibt: Welche Programmteile des zu testenden Programms sollten mit Hilfe von Stubs verschattet werden, und was darf getrost benutzt werden, obwohl es nicht in der Zuständigkeit des eigenen Moduls liegt? Das Kriterium dafür ist die isolierte Testbarkeit des Moduls: Liefert mein Modultest beispielsweise in jedem Mandanten des Systems die gleichen Ergebnisse? Würde er bei Import in ein ganz anderes SAP-System immer noch das gleiche Ergebnis liefern?

Nehmen wir als Beispiel eine Customizingtabelle, deren Werte im eigenen Code benötigt werden. Man könnte in der setup-Methode den Eintrag in der Datenbanktabelle für Testzwecke "präparieren" und im teardown den alten Zustand wiederherstellen. Das ist aber kein empfehlenswertes Vorgehen. Customizingtabellen werden meist nicht direkt selektiert, sondern man verwendet einen eigens dafür vorgesehenen Lesebaustein. Dieser fremde Code kann aber weitere applikatorische Prüfungen enthalten, zum Beispiel auf Konsistenz der zu lesenden mit einer anderen Customizingtabelle. Dadurch entstehen weitere Abhängigkeiten. Ausserdem puffern Lesebausteine zumindest die gerade gelesene Zeile für den aktuellen internen Modus, so dass man im Test den Lesebaustein mit der Option bypassing_buffer = 'X' lesen muss (natürlich wieder vorgängig im Setup, denn im produktiven Coding soll ja vom Puffer profitiert werden).

Fazit: Das Lesen von Customizingtabellen sollte unbedingt über eine lokale Klasse umgeleitet werden, die man im Test durch einen Stub ersetzt. Dasselbe gilt erst recht für Anwendungstabellen.

Gibt es Ausnahmen? Ja: Für eine Tabelle zuständige Objekte (DAOs). Der Entwurf des DAO sieht vor, dass alle Zugriffe (schwache Form: zumindest alle ändernden Zugriffe) auf die betreffende Datenbanktabelle über dessen Methoden erfolgen sollen. In diesem Fall können die Unit Tests Einträge in der Tabelle erzeugen und im teardown wieder löschen. Denn die Operationen auf der eigenen Datenbanktabelle stellen ja gewissermassen die Kernkompetenz des DAO dar, und die Unabhängigkeit des Tests von anderen Softwareteilen und von der Systemumgebung ist gewährleistet. Am besten verwendet man dabei spezielle Testwerte für die Schlüsselfelder, um nicht mit allfälligen Integrationstests auf dem Entwicklungssystem zu kollidieren.

Ebenso ist es mit Aufrufen von Code. Der in Sprachkonstrukten implizit verborgene Code lässt sich offensichtlich nicht ausblenden. Es gibt aber auch Basis-Code, der nicht in der Programmiersprache enthalten ist und dessen Verwendung in Unit Tests i.a. unbedenklich ist. Hierzu gehören beispielsweise das Runtime Type Identification System (RTTI), also die Klasse cl_abap_typedescr und Verwandte, oder Funktionsbausteine, die einen Range in eine Where-Bedingung konvertieren. Als eine Faustregel könnte man sagen, dass eigentlich aller Code der Softwarekomponente SAP_BASIS auch in Unit Tests getrost ausgeführt werden kann, ohne die Unabhängigkeit preiszugeben. Aber schon bei der Anwendungsbasis (SAP_ABA) ist das nicht mehr so sicher. Die Zentrale Adressverwaltung stellt mit Sicherheit schon tiefste Anwendungslogik dar, und auch die Statusverwaltung hängt von so vielen Customizingtabellen ab, dass man in Tests besser mit Objekten arbeiten sollte, die ihr Verhalten simulieren.

[1] Damir Majer, Unit Tests mit ABAP Unit, dpunkt Verlag, Heidelberg 2008.

Keine Kommentare :