Unit Tests für RAP Business Objects
Aktualisiert: 29. Sept.
ABAP Unit kennen die meisten EntwicklerInnen. Man instanziiert eine normale Klasse, ruft eine Methode auf und prüft das Ergebnis. Bei einem RAP Business Object funktioniert das so nicht. Der Zugriff läuft über EML, die Daten landen zuerst im Transactional Buffer und die eigentliche Logik greift oft erst in der Save Sequence. Wer ein RAP BO testen will, braucht deshalb eine Testumgebung, die diesen Lifecycle abbildet und die Datenbankzugriffe mockt.
In diesem Beitrag bauen wir eine vollständige Testklasse auf: Setup der Testumgebung,
CRUD Operationen, Validations und Actions.
Das Beispiel
Als Beispiel dient ein bewusst kleines, Draft-fähiges Business Object ZR_REVIEW mit den
Feldern text, author, status und rating. Damit die Tests nachvollziehbar bleiben, hier der
relevante Ausschnitt aus der Behavior Definition:

Drei Dinge daraus sind für die Tests relevant: die Persistenztabelle zreview und die Draft-
Tabelle zreview_d, die Validation validateAuthor, die auf save läuft sowie die beiden Actions
setStatus (instanzgebunden, mit Parameter) sowie resetAll (statisch, an die Entität
gebunden).
Lokale oder globale Testklasse
Für RAP Unit Tests gibt es zwei Ansätze:
Die lokale Testklasse liegt direkt im Behavior Pool im Tab “Test Classes”. Über LOCAL
FRIENDS bekommt sie Zugriff auf die privaten Methoden der Handler Klasse. Sie ist erste
Wahl, wenn wir die Implementierung des BO absichern wollen, und sie wandert
automatisch mit dem Behavior Pool durch den Transportweg.
Die globale Testklasse testet das BO von außen wie ein beliebiger Consumer. Das eignet
sich für Szenario- und Integrationstests über mehrere Objekte hinweg.
Wir nutzen hier die lokale Variante.
Die Testumgebung: zwei Environments, nicht eines
Der häufigste Fehler beim Einstieg: Es wird nur CL_CDS_TEST_ENVIRONMENT aufgesetzt. Das
reicht nicht.
CL_CDS_TEST_ENVIRONMENT deckt die Lesezugriffe ab. Wichtig für das Verständnis: Das CDS Test Double Framework doppelt nicht die CDS Entität selbst, sondern deren Abhängigkeiten.
Übergeben wird die CDS Entität, ersetzt werden die darunter liegenden Datenquellen.
Genau deshalb haben unsere Mock-Daten die Struktur der Tabelle zreview und nicht die der
View.
CL_OSQL_TEST_ENVIRONMENT erzeugt Test Doubles für die Datenbanktabellen im ABAP SQL
Zugriff. Das brauchen wir, sobald geschrieben wird. Ein Update ohne OSQL Environment
schlägt fehl, weil der Datensatz beim Lesen aus der Tabelle nicht gefunden wird. Auch die
abschließende Verifikation per SELECT in der Testmethode läuft gegen dieses Double.
In die Dependency List gehören die Persistenztabelle und, weil unser BO Draft-fähig ist,
zusätzlich die Draft-Tabelle. Fehlt die Draft-Tabelle, laufen Draft-Szenarien gegen die echte
Datenbank.

RISK LEVEL HARMLESS und DURATION SHORT sind hier kein Beiwerk. Nur mit diesen Angaben laufen die Tests in ATC Runs und in den üblichen Qualitätsgates der Transportfreigabe
automatisch mit.
Setup und Teardown
Die Aufteilung ist wichtig, damit die Tests unabhängig voneinander bleiben:
class_setup baut beide Environments einmalig auf. Das ist der teure Teil und gehört
nicht in jede Testmethode.
setup leert die Doubles und legt vor jedem einzelnen Test frische Mock-Daten an.
teardown setzt mit ROLLBACK ENTITIES alle Änderungen zurück. Ohne diese Zeile sieht
der nächste Test die Daten des vorherigen.
class_teardown räumt die Environments wieder ab.

Die Mock-Daten werden bewusst in beide Environments eingefügt. Das CDS Double bedient
die Lesezugriffe des BO, das OSQL Double die Schreibpfade und unsere Prüf-Selects.
Hängen mehrere CDS Entitäten am Test, etwa eine Composition aus Kopf und Position,
nutzen wir statt create die Variante create_for_multiple_cds und übergeben alle beteiligten
Entitäten:

Über den optionalen Parameter i_select_base_dependencies lässt sich steuern, ob die Doubles
auf Ebene der Basistabellen oder der direkten Vorgänger-Views erzeugt werden. Das ist
relevant, sobald die CDS Entität über mehrere Stufen aufgebaut ist.
CRUD testen
Jede Testmethode folgt demselben Dreischritt: EML Operation ausführen, COMMIT ENTITIES
auslösen, Ergebnis prüfen.
Der COMMIT ENTITIES ist nicht optional. Erst dort läuft die Save Sequence, erst dort greifen
Validations und Determinations, und erst dort werden die Daten in die gemockten Tabellen
geschrieben. Wer nur auf das FAILED des MODIFY prüft, testet die halbe Wahrheit.
Create

Zwei Details lohnen die Aufmerksamkeit. Die %control Flags müssen für jedes Feld gesetzt
sein, das übergeben wird, sonst ignoriert das Framework den Wert stillschweigend. Und
der erzeugte Key kommt aus der MAPPED Struktur über die %cid. Bei einer Early Numbering
Implementierung ist das der einzige Weg an den Schlüssel.
Update

UPDATE FIELDS ( status ) grenzt die Operation auf ein Feld ein. Das ist präziser als ein Full
Update und deckt auf, wenn im Behavior versehentlich mehr geschrieben wird als erlaubt.
Delete

Beim Delete prüfen wir bewusst auf sy-subrc = 4 statt auf ein leeres Ergebnisfeld. Das ist die
eindeutigere Aussage: Der Satz existiert nicht mehr.
Validations testen
CRUD abzusichern ist der Anfang. Die Business-Logik steckt in Validations, Determinations
und Actions, und genau dort zahlt sich Testabdeckung aus.
Wir nehmen die Validation validateAuthor, die einen leeren Author ablehnt. Getestet wird
immer in beide Richtungen. Ein Test, der nur den Gutfall prüft, würde auch dann grün
bleiben, wenn die Validation gar nicht mehr aufgerufen wird.
Wichtig ist der Prüfzeitpunkt: Validations laufen in der Save Sequence, nicht beim MODIFY.
Die Assertions gehören deshalb auf _commit_failed und _commit_reported, nicht auf die
Rückgaben der Modify-Anweisung.
Negativtest: Author leer

Positivtest: Author gefüllt

Wer die Messages selbst prüfen will, kann statt assert_not_initial auf die Message-ID und
die Nummer im %msg der Reported-Struktur zugreifen. Das macht den Test schärfer, koppelt
ihn aber an den Nachrichtentext. In der Praxis ist die Prüfung auf Message-ID und Nummer
ein guter Kompromiss.
Actions testen
Actions werden in EML über EXECUTE aufgerufen. Wir schauen uns beide Varianten an.
Instance Action: setStatus
Eine Instance Action hängt an einem konkreten Datensatz. Der Schlüssel kommt über %tky,
die Parameter über %param.

Static Action: resetAll
Eine Static Action ist nicht an eine Instanz gebunden, sondern an die Entität. Es gibt also
keinen %tky. Stattdessen muss %cid gesetzt sein. Fehlt die Angabe, quittiert das Framework
den Aufruf mit einer BEHAVIOR_CONTRACT_VIOLATION und dem Grund MISSING_CID. Das ist eine der Fehlermeldungen, an denen man beim ersten Mal zuverlässig hängen bleibt.

Tests ausführen
Die Tests starten in ADT über Ctrl+Shift+F10. Das Ergebnis erscheint im Tab “ABAP Unit”.
Grün bedeutet, dass alle Assertions gehalten haben. Rot zeigt, welche Assertion gebrochen
ist, mit erwartetem und tatsächlichem Wert.
Für die Abdeckung lohnt sich zusätzlich der ABAP Coverage Run über Ctrl+Shift+F11. Er
zeigt, welche Zweige des Behavior Pools tatsächlich durchlaufen wurden. Erfahrungsgemäß
fallen dabei genau die Fehlerpfade auf, für die noch kein Negativtest existiert.
Typische Stolpersteine
Nur CL_CDS_TEST_ENVIRONMENT aufgesetzt: Lesen funktioniert, Schreiben scheitert
Kein ROLLBACK ENTITIES im Teardown: Tests beeinflussen sich gegenseitig und die
Reihenfolge entscheidet über Grün oder Rot
Assertion auf das FAILED des MODIFY statt auf das des COMMIT: Validations und
Determinations laufen erst in der Save Sequence
Fehlendes %cid bei Static Actions: BEHAVIOR_CONTRACT_VIOLATION mit MISSING_CID
Fehlende %control Flags: Werte werden ohne Fehlermeldung verworfen
Draft-Tabelle nicht in der Dependency List: Draft-Szenarien laufen gegen die echte
Datenbank
Environments in setup statt class_setup erzeugt: die Laufzeit der Testsuite steigt
spürbar
FAZIT
RAP Business Objects sind testbar, sie brauchen nur das passende Fundament. Zwei Test
Environments, ein sauberes Setup und Teardown, und danach folgt jede Testmethode
demselben Muster aus EML Operation, Commit und Assertion.
Der eigentliche Gewinn entsteht nicht bei den CRUD Tests, sondern bei Validations und
Actions. Dort steckt die Business-Logik, dort ändert sich über die Laufzeit eines Projekts am
meisten, und dort verhindert eine automatisierte Testsuite die Regressionen, die sonst erst
im Test- oder Produktivsystem auffallen. Für Clean Core Entwicklungen, die regelmäßig
gegen neue Releases laufen müssen, ist das kein optionaler Komfort, sondern
Voraussetzung.
Sie möchten Ihre RAP Entwicklungen automatisiert absichern oder eine bestehende
Codebasis testbar machen? Pure Consulting unterstützt Sie von der Teststrategie über den
Aufbau der Testumgebung bis zur Integration in Ihre Transport- und Qualitätsprozesse.
Pure Consulting macht Ihre RAP-Entwicklungen fit für automatisierte Tests, von der Teststrategie bis zur nahtlosen Integration in Ihre Qualitätsprozesse.
Häufige Fragen:
Warum reicht CL_CDS_TEST_ENVIRONMENT nicht aus?
Es mockt nur die CDS Views und damit die Lesezugriffe. Schreibende Operationen greifen auf die Datenbanktabellen zu. Dafür wird zusätzlich CL_OSQL_TEST_ENVIRONMENT benötigt.
Warum ist COMMIT ENTITIES im Test nötig?
Validations, Determinations und die Save Sequence laufen erst beim Commit. Ohne Commit bleiben die Daten im Transactional Buffer und die Business-Logik wird nie ausgeführt.
Lokale oder globale Testklasse?
Die lokale Testklasse im Behavior Pool eignet sich für die Absicherung der Implementierung, inklusive Zugriff auf private Methoden. Die globale Testklasse testet das BO von außen und passt für Szenario- und Integrationstests.
Was bedeutet MISSING_CID?
Bei Static Actions fehlt die %cid. Sie ist Pflicht, weil die Zuordnung von Ergebnis und Aufruf ohne Instanzschlüssel darüber erfolgt.
Laufen die Tests gegen echte Daten?
Nein. Beide Test Environments erzeugen Test Doubles. Die Mock-Daten werden pro Test frisch gesetzt und nach jedem Test per ROLLBACK ENTITIES verworfen.

