RAP - ABAP Unit (Service)
Wie testet man den Service einer Schnittstelle oder RAP App eigentlich automatisch? In diesem Artikel schauen wir uns Unit Tests für OData Services an.
Inhaltsverzeichnis
Im letzten Artikel hatten wir uns ABAP Unit für das RAP Business Objekt angeschaut und wie du damit die verschiedenen Funktionen automatisch testen kannst. Heute schauen wir uns die Automatisierung der Tests für den OData Service an und wie du die Umsetzung am besten durchführst.
Test schreiben
Im ersten Schritt beginnen wir wieder mit der Hülle der Testklasse und verwenden dabei eine globale Klasse für den Test, die wir über ABAP Doc mit dem Service verbinden, den wir testen. Die Hülle der Klasse sieht wie folgt aus:
"! @testing ZBS_UI_SIMPLE_PARTNER_O2
CLASS zcl_bs_demo_unit_service DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PUBLIC SECTION.
PROTECTED SECTION.
PRIVATE SECTION.
TYPES:
BEGIN OF ts_key,
PartnerNumber TYPE ZBS_C_RAPPartner-PartnerNumber,
END OF ts_key.
METHODS:
get_all_partners FOR TESTING RAISING cx_static_check,
update_unison FOR TESTING RAISING cx_static_check,
delete_nevada_inc FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS zcl_bs_demo_unit_service IMPLEMENTATION.
METHOD get_all_partners.
ENDMETHOD.
METHOD update_unison.
ENDMETHOD.
METHOD delete_nevada_inc.
ENDMETHOD.
ENDCLASS.
Wir definieren drei Tests, um die Funktionen des Service zu validieren:
- Lesen aller Datensätze
- Anpassung eines Datensatzes
- Löschen eines Datensatzes
Bevor wir allerdings mit dem Test beginnen, benötigen wir einen Testclient für jeden Test. Hier hast du die Möglichkeit einen für OData V2 oder V4 anzulegen. Da wir unseren V2 Service testen wollen, sieht die Erzeugung wie folgt aus, dabei übergeben wir den Service und die Version:
ro_result = cl_web_odata_client_factory=>create_v2_local_proxy(
VALUE #( service_id = 'ZBS_UI_SIMPLE_PARTNER_O2' service_version = '0001' )
).
Hinweis: Über die Klasse kannst du verschiedene Arten von Clients erzeugen, der Local Proxy wird für die Tests benötigt.
Lesen
Nachdem wir uns einen Testclient erzeugt haben, erzeugen wir einen Lese-Request auf der Partner Entität, führen diesen ohne Filter aus und holen uns das Ergebnis zurück. Wir prüfen, ob die Tabelle befüllt ist und ein gewisser Eintrag vorhanden ist:
DATA:
lt_result TYPE TABLE OF ZBS_C_RAPPartner.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner' )->create_request_for_read( ).
DATA(lo_response) = lo_request->execute( ).
lo_response->get_business_data( IMPORTING et_business_data = lt_result ).
cl_abap_unit_assert=>assert_not_initial( lt_result ).
cl_abap_unit_assert=>assert_not_initial( lt_result[ PartnerName = 'Unison' ] ).
Aktualisieren
Um einen Datensatz zu aktualisieren, erzeugen wir uns einen Update Request über den Client, dazu müssen wir nach der Entität den Schlüssel mitgeben und die Operation PUT wählen. Im Anschluss übergeben wir die neuen Daten und führen die Anfrage aus. Am Ende prüfen wir gegen die Datenbank, ob der Datensatz tatsächlich geändert wurde.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner'
)->navigate_with_key( VALUE ts_key( PartnerNumber = '3000000003' )
)->create_request_for_update( /iwbep/if_cp_request_update=>gcs_update_semantic-put ).
lo_request->set_business_data( VALUE ZBS_C_RAPPartner( Street = 'Updated' ) ).
lo_request->execute( ).
SELECT SINGLE FROM zbs_dmo_partner
FIELDS *
WHERE partner = '3000000003'
INTO @DATA(ls_result).
cl_abap_unit_assert=>assert_equals( act = ls_result-street exp = 'Updated' ).
Löschen
Das Löschen funktioniert so ähnlich wie die Aktualisierung, nur das wir am Ende einen Request fürs Löschen erstellen und keine Daten übergeben. Am Ende überprüfen wir, ob der Datensatz von der Datenbank gelöscht wurde.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner'
)->navigate_with_key( VALUE ts_key( PartnerNumber = '3000000001' )
)->create_request_for_delete( ).
lo_request->execute( ).
SELECT SINGLE FROM zbs_dmo_partner
FIELDS *
WHERE partner = '3000000001'
INTO @DATA(ls_partner).
cl_abap_unit_assert=>assert_subrc( exp = 4 ).
Hinweis: Je nachdem welche Features du implementiert hast, wird Update oder Delete nicht funktionieren im Test. Ohne implementierte Feature Control, funktionieren die Test wie gewohnt.
Daten mocken
Bei den Tests verwenden wir aktuell die echten Daten von der Datenbank, was aber nicht für wiederholbare Tests spricht. Deshalb sollten wir die Daten mocken, sodass wir für jeden Test die gleichen Daten verwenden und damit auch sicherstellen, dass die Daten immer zur Verfügung stehen und zwar in dem Stand, den wir benötigen. Dazu definieren wir in der PRIVATE Section noch folgende Variable und Methoden:
CLASS-DATA:
go_environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS:
class_setup RAISING cx_static_check,
class_teardown.
Dann können wir CLASS_SETUP implementieren, in diesem Fall erzeugen wir einen CDS Double für den Comsumption View, da der Serivce auf dieser View arbeitet. Als abhängiges Objekt wird auch die Tabelle mit ihren Daten versorgt. Zum Abschluss aktivieren wir noch den Double, damit die Daten korrekt gezogen werden:
DATA:
lt_partner TYPE STANDARD TABLE OF zbs_dmo_partner WITH EMPTY KEY.
go_environment = cl_cds_test_environment=>create(
i_for_entity = 'ZBS_C_RAPPARTNER'
i_dependency_list = VALUE #( ( name = 'ZBS_DMO_PARTNER' type ='TABLE' ) )
).
lt_partner = VALUE #(
( partner = '3000000001' name = 'Nevada Inc' country = 'US' payment_currency = 'USD' )
( partner = '3000000002' name = 'REWE' street = 'Main street 10' country = 'DE' payment_currency = 'EUR' )
( partner = '3000000003' name = 'Unison' street = 'Side road 16' country = 'AU' payment_currency = 'AUD' )
).
go_environment->insert_test_data( lt_partner ).
go_environment->enable_double_redirection( ).
Im CLASS_TEARDOWN kümmern wir uns im Anschluss darum, dass der CDS Double auch wieder sauber entfernt wird:
go_environment->destroy( ).
Die Daten sind damit nun für jeden Testfall sauber hinterlegt und du brauchst dir keine Sorgen zu machen, wie die Daten auf der Datenbank aussehen. Am Ende erhältst du immer die aktuellen Testdaten für deine Testfälle.
Komplettes Beispiel
Das vollständige Beispiel findest du wieder hier am Ende des Artikels und umfasst die komplette Testklasse:
"! @testing ZBS_UI_SIMPLE_PARTNER_O2
CLASS zcl_bs_demo_unit_service DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PUBLIC SECTION.
PROTECTED SECTION.
PRIVATE SECTION.
TYPES:
BEGIN OF ts_key,
PartnerNumber TYPE ZBS_C_RAPPartner-PartnerNumber,
END OF ts_key.
CLASS-DATA:
go_environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS:
class_setup RAISING cx_static_check,
class_teardown.
METHODS:
get_all_partners FOR TESTING RAISING cx_static_check,
update_unison FOR TESTING RAISING cx_static_check,
delete_nevada_inc FOR TESTING RAISING cx_static_check,
create_test_client
RETURNING VALUE(ro_result) TYPE REF TO /iwbep/if_cp_client_proxy
RAISING
cx_static_check.
ENDCLASS.
CLASS zcl_bs_demo_unit_service IMPLEMENTATION.
METHOD class_setup.
DATA:
lt_partner TYPE STANDARD TABLE OF zbs_dmo_partner WITH EMPTY KEY.
go_environment = cl_cds_test_environment=>create(
i_for_entity = 'ZBS_C_RAPPARTNER'
i_dependency_list = VALUE #( ( name = 'ZBS_DMO_PARTNER' type ='TABLE' ) )
).
lt_partner = VALUE #(
( partner = '3000000001' name = 'Nevada Inc' country = 'US' payment_currency = 'USD' )
( partner = '3000000002' name = 'REWE' street = 'Main street 10' country = 'DE' payment_currency = 'EUR' )
( partner = '3000000003' name = 'Unison' street = 'Side road 16' country = 'AU' payment_currency = 'AUD' )
).
go_environment->insert_test_data( lt_partner ).
go_environment->enable_double_redirection( ).
ENDMETHOD.
METHOD class_teardown.
go_environment->destroy( ).
ENDMETHOD.
METHOD create_test_client.
ro_result = cl_web_odata_client_factory=>create_v2_local_proxy(
VALUE #( service_id = 'ZBS_UI_SIMPLE_PARTNER_O2' service_version = '0001' )
).
ENDMETHOD.
METHOD get_all_partners.
DATA:
lt_result TYPE TABLE OF ZBS_C_RAPPartner.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner' )->create_request_for_read( ).
DATA(lo_response) = lo_request->execute( ).
lo_response->get_business_data( IMPORTING et_business_data = lt_result ).
cl_abap_unit_assert=>assert_not_initial( lt_result ).
cl_abap_unit_assert=>assert_not_initial( lt_result[ PartnerName = 'Unison' ] ).
ENDMETHOD.
METHOD update_unison.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner'
)->navigate_with_key( VALUE ts_key( PartnerNumber = '3000000003' )
)->create_request_for_update( /iwbep/if_cp_request_update=>gcs_update_semantic-put ).
lo_request->set_business_data( VALUE ZBS_C_RAPPartner( Street = 'Updated' ) ).
lo_request->execute( ).
SELECT SINGLE FROM zbs_dmo_partner
FIELDS *
WHERE partner = '3000000003'
INTO @DATA(ls_result).
cl_abap_unit_assert=>assert_equals( act = ls_result-street exp = 'Updated' ).
ENDMETHOD.
METHOD delete_nevada_inc.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner'
)->navigate_with_key( VALUE ts_key( PartnerNumber = '3000000001' )
)->create_request_for_delete( ).
lo_request->execute( ).
SELECT SINGLE FROM zbs_dmo_partner
FIELDS *
WHERE partner = '3000000001'
INTO @DATA(ls_partner).
cl_abap_unit_assert=>assert_subrc( exp = 4 ).
ENDMETHOD.
ENDCLASS.
Fazit
Neben dem Testen des RAP Objekts, kannst du nun auch den Service testen und auch mit Testdaten bestücken, ohne auf den echten Daten zu Arbeiten. Im RAP Umfeld gibt es diese beiden Arten von Tests (Business Objekt und Service).