Modernes ABAP

ABAP Unit - Test Doubles

(C) Brandeis Consulting.

Test-Doubles

Aus dem Blog: ABAP Test Double Framework - An Introduction

(C) Brandeis Consulting.

Unterschiedliche Test-Doubles

Es gibt unterschiedliche Arten von Test-Doubles:

  • Dummy - Ein leeres Objekt, das keine Funktion hat. Außer das es die notwendigen Schnittstellen hat.
  • Stub - Ein Objekt das vorgegebene Daten zurückgibt
  • Mock-Objekt - Ein Objekt das komplexere Logik implementiert

Test Doubles können manuell erzeugt werden. Das ist aber aufwändig. Alternativ gibt es mehrere Mocking-Frameworks:

(C) Brandeis Consulting.

Das SAP Test Double Framework

Für alle Objekte, kann man ein Test-Double erstellen. Dazu ruft man im einfachsten Falle die Methode
CL_ABAP_TESTDOUBLE=>CREATE( <Interfacename> )
auf. Der RETURN Wert dieser Methode kann auf eine passende Referenzvariable gecastet werden:

    DATA lo_sorter_double TYPE REF TO zif_sort.

    "Erzeuge ein Test Double Objekt:
    lo_sorter_double = CAST zif_sort( cl_abap_testdouble=>create( 'ZIF_SORT' ) ).

Die Implementierung ist zunächst ein Dummy, d.h. ohne Funktion. Alle Methoden des Interfaces lassen sich aufrufen, aber es wird nie etwas zurückgegeben.

Ab 7.53 ist es auch möglich, dass man den Namen eine globalen Klasse übergibt.

(C) Brandeis Consulting.

Vom Dummy zum Stub

Wenn das Double Objekt Daten zurückgeben soll, dann müssen die ganz konkreten Methodenaufrufe zuvor konfiguriert werden:

  1. Zunächst wird der Rückgabewert definiert
  2. Danach wird dann in einem Aufruf des Dummy Objektes mit den entsprechenden Parameterwerten festgelegt, wann die zuvor definierten Rückgabewerte zurückgegeben werden solle.
    "Definiere Rückgabewert....
    cl_abap_testdouble=>configure_call( lo_sorter_double )->returning( 
                         VALUE zif_sort=>tt_sort(  ( `Ali` )
                                                   ( `Jörg` )
                                                  ( `Manfred` ) ) ).
    "... für diesen Methodenaufruf:
    lo_sorter_double->sort( VALUE #( ( `Jörg` )
                                     ( `Ali` )
                                     ( `Manfred` ) ) ).

Für alle anderen Werte werden initiale Daten zurückgegeben.

(C) Brandeis Consulting.

Vollständiges Beispiel mit Test-Double

CLASS ltcl_test DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.
  PUBLIC SECTION.
    METHODS constructor.
    
  PRIVATE SECTION.
    DATA cut TYPE REF TO zcl_call_sort.
    METHODS:
      test_create_sorted_list FOR TESTING RAISING cx_static_check    .
ENDCLASS.


CLASS ltcl_test IMPLEMENTATION.

  METHOD test_create_sorted_list.
    DATA(result) = cut->create_sorted_list( VALUE #( ( `Jörg` )
                                                     ( `Ali` )
                                                     ( `Manfred` ) ) ).

    cl_abap_unit_assert=>assert_equals( act = result
                                        exp = `Ali, Jörg, Manfred` ).
  ENDMETHOD.

  METHOD constructor.
    DATA lo_sorter_double TYPE REF TO zif_sort.

    "Erzeuge ein Test Double Objekt:
    lo_sorter_double = CAST zif_sort( cl_abap_testdouble=>create( 'ZIF_SORT' ) ).

    "Definiere Rückgabewert....
    cl_abap_testdouble=>configure_call( lo_sorter_double )->returning( 
                         VALUE zif_sort=>tt_sort(  ( `Ali` )
                                                   ( `Jörg` )
                                                  ( `Manfred` ) ) ).
    "... für diesen Methodenaufruf:
    lo_sorter_double->sort( VALUE #( ( `Jörg` )
                                     ( `Ali` )
                                     ( `Manfred` ) ) ).

    cut = NEW zcl_call_sort( lo_sorter_double ).

  ENDMETHOD.

ENDCLASS.
(C) Brandeis Consulting.

ABAP SQL Test Double Framework (SQL TDF)

Das ABAP SQL Test Double Framework kann gezielt einen Datenbankzustand herstellen, der nur während der Ausführung des Tests gilt.

Mit dem SQL TDF wird der normale Datenbankzustand durch die Double-Version ersetzt. Nicht Committete Daten gehen dabei ggf. verloren.

Das OSQL Test Environment darf nur einmal pro Testklasse existieren!

Das SQL TDF greifft nur bei direkten SELECT-Zugriffen aus dem ABAP auf die Tabelle. Falls ein CDS View dazwischen liegt, dann muss das CDS TDF involviert werden.

(C) Brandeis Consulting.

ABAP SQL Test Double Framework


    methods select_data 
       importing iv_project type zbc_project_key
       RETURNING VALUE(result) type tt_tasks.
...
  METHOD SELECT_DATA.

    select *
      from zbc_tasks
      where project = @iv_project
      into table @result .


  ENDMETHOD.
  method t_select.

    data(environment) = cl_osql_test_environment=>create( 
      i_dependency_list = value #( (  'ZBC_TASKS' ) ) ).
    data(mock_data)   = 
    value zbc_ut_demo=>tt_tasks( client = sy-mandt 
                            ( project = 'A' task_key = 'A-1' )
                            ( project = 'B' task_key = 'B-1' )
                            ( project = 'A' task_key = 'A-2' ) )  .
    environment->insert_test_data( i_data = mock_data ).
    data(result) = new zbc_ut_demo( )->select_data( 'A' ).

    cl_abap_unit_assert=>assert_equals( 
                  exp = value zbc_ut_demo=>tt_tasks( 
                       client = sy-mandt ( project = 'A' task_key = 'A-1' )
                                         ( project = 'A' task_key = 'A-2' ) )
                  act = result ).

  endmethod.
(C) Brandeis Consulting.

Herausforderungen beim SQL Test Double Framework

Nur ein Test Environment pro Testklasse

Das Test Environment darf nur einmal pro Testklasse instanziiert werden. Der Zustand der DB-Tabellen bleibt zwischen den Tests erhalten.

  • Das Test Environment muss ggf. in einer statischen Variable (CLASS-DATA) gespeichert und in der CLASS_SETUP-Methode initialisiert werden
  • Falls eine Testmethode die Testdaten mit insert_test_data( ) direkt setzt, sollte vorher immer clear_doubles( ) aufgerufen werden.
  • Jedes Environment hat eine feste Menge an Tabellen. Bei Tests mit unterschiedlichen Tabellen benötigt man unterschiedliche Testklassen.

Empfehlung

Für jedes Fixture (Testszenario) eine separate Testklasse anlegen. Das Testszenario kann dan im class_setup aufgebaut werden.

(C) Brandeis Consulting.

Übung DAO und SQL Test Double Framework

Lege eine Data Access Objekt Klasse für die Benutzertabelle ZBC_USERS an.

CLASS zcl_dao_users DEFINITION PUBLIC FINAL CREATE PUBLIC .

  PUBLIC SECTION.
    TYPES tt_user TYPE TABLE OF zbc_users WITH DEFAULT KEY.
    METHODS get_user_data IMPORTING user_id       TYPE zbc_user_id
                          RETURNING VALUE(Result) TYPE zbc_users
                          RAISING   zcx_not_found.
    METHODS get_active_users RETURNING VALUE(Result) TYPE tt_user.
ENDCLASS.

Die Methode

  • GET_USER_DATA soll die Daten eines Benutzers liefern. Falls dieser nicht vorhanden ist, dann soll die entsprechende Exception ausgelöst werden.
  • GET_ACTIVE_USERS soll die Daten aller Benutzer liefern, die mindestens eine offene Aufgabe haben. Also Aufgaben, die nicht im Status CANCELED, DELETED oder FIXED sind.

Definiere vor der Implementierung der Methoden die entsprechenden Unit Tests an.

(C) Brandeis Consulting.

SAP ABAP CDS Test Double Framework (CDS TDF)

Dokumentation

Das CDS TDF ermöglicht den Test von Logik in CDS-Views. Die Herausforderung hier ist, dass diese in der Datenbank ausgeführt wird und nicht auf ABAP Seite.

Darüber hinaus können mit dem CDS TDF auch die Daten von CDS Views gesetzt werden, die in ABAP SELECTs verwendet werden.

Das CDS TDF hat einen brauchbaren Assistenten zum Erstellen der Testdaten:

(C) Brandeis Consulting.

Übung

Lege einen Test für die CDS View Entity ZI_TASKS an. Dieser hat eine Logik um aus der Prio-Angabe von 0 (unwichtig) bis 100(dringend) eine Schulnote von 1 (dringend) bis 6 (unwichtig) zu berechnen.

Leider ist da ein Fehler drin. Schreibe mit Hilfe des Assistenten einen Test, um den Fehler zu entdecken.

Wer will, kann sich die CDS View Entity auch selber anlegen bzw. kopieren und den Code korrigieren.

(C) Brandeis Consulting.

CDS TDF & SQL TDF

Vorsicht: SQL TDF ist für CDS nicht wirksam.
(C) Brandeis Consulting.