This is a test message to test the length of the message box.
Login
ABAP in der Praxis Test Driven Development
Erstellt von Software-Heroes

ABAP in der Praxis - Test Driven Development

213

Wie funktioniert eigentlich TDD in der Praxis und gibt es einfache Beispiele zum Lernen in ABAP? In dieser Übung gehen wir auf den praktischen Teil ein.

Werbung


In diesem Artikel schauen wir uns einmal ein praktisches und spielerisches Beispiel zum Umsetzen von Test Driven Development an und wie du es in deiner Arbeit einsetzen könntest.

 

Einleitung

Dazu wollen wir vor der eigentlichen Entwicklung zuerst einmal unsere Unit Tests implementieren und sehen, dass diese auf "Rot" laufen. Im Anschluss beginnen wir mit der Implementierung der eigentlichen Logik und bringen die Unit Tests in den "grünen" Zustand. Mit den Tests wollen wir zuerst alle Use-Cases und Anforderungen abdecken, um dann die Lösung zu entwickeln. Diese Methodik der Entwicklung ist besonders schwer umzusetzen, da wir als Entwickler uns vorher Gedanken machen müssen, was wir benötigen. Damit erreichen wir aber auch drei Ziele:

  • Die Unit Tests funktionieren (Rot -> Grün)
  • Unser Code ist testbar geschrieben
  • Unit Tests sind vorhanden

 

Möchtest du mehr über TDD erfahren, findest du einen guten Artikel auf Wikipedia.

 

Vorbereitung

In dieser Aufgabe wollen wir einen Konverter für römische Zahlen erstellen, um die Nummern in beide Richtungen zu konvertieren. Dazu benötigen wir eine Klasse, die zwei Methoden für die Konvertierung hat. Anhand dieser Basis können wir dann die Implementierung vornehmen. In diesem Beispiel verwenden wir eine Exception Klasse wieder, um Sonderfälle in den Methoden abzufangen.

CLASS zcl_bs_demo_roman_numbers DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS convert_roman_to_arabic
      IMPORTING id_roman         TYPE string
      RETURNING VALUE(rd_result) TYPE i
      RAISING   cx_abap_not_in_allowlist.

    METHODS convert_arabic_to_roman
      IMPORTING id_arabic        TYPE i
      RETURNING VALUE(rd_result) TYPE string
      RAISING   cx_abap_not_in_allowlist.
      
  PRIVATE SECTION.
ENDCLASS.


CLASS zcl_bs_demo_roman_numbers IMPLEMENTATION.
  METHOD convert_arabic_to_roman.
  ENDMETHOD.


  METHOD convert_roman_to_arabic.
  ENDMETHOD.
ENDCLASS.

 

Aufgabe

Die Aufgabe besteht nun darin, die Klasse mit Test Driven Development zu implementieren. Dabei wollen wir im ersten Schritt die Unit Tests implementieren, um dann Stück für Stück die Logik zu entwickeln und die Vorteile von TDD kennenzulernen. Dabei gelten die folgenden Regeln für die Konvertierung:

  • Es gibt die folgenden römischen Zahlen ...
    • I - 1
    • V - 5
    • X - 10
    • L - 50
    • C - 100
    • D - 500
    • M - 1000
  • Maximal drei gleiche Zeichen nacheinander, wobei V/L/D alleinstehen.
  • Subtraktionsregel - Steht eine kleinere Zahl, vor einer größeren Zahl, wird die kleinere von der größeren abgezogen.
  • Bereich - Die kleinste Römische Zahl ist 1 und die größte 3999, ohne die Regeln zu verletzen.

 

Hinweis: Im nächsten Abschnitt werden wir auf die Lösung eingehen, wenn du die Aufgabe erst einmal selbstständig machen möchtest, solltest du hier pausieren.

 

Lösung

In diesem Abschnitt gehen wir Stück für Stück durch die Lösung und implementieren die verschiedenen Bestandteile.

 

Unit Tests

Beginnen wir also im ersten Schritt mit unseren Unit Tests. Dazu implementieren wir eine erste Testklasse, die sich um die Umwandlung der römischen Zahlen in die arabischen Zahlen beschäftigt. Für die andere Methode würden wir entsprechend eine zweite Testklasse erzeugen.

CLASS ltc_roman_to_arabic DEFINITION FINAL
  FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.

  PRIVATE SECTION.
    DATA mo_cut TYPE REF TO zcl_bs_demo_roman_numbers.

    METHODS setup.

    METHODS convert_i FOR TESTING.
ENDCLASS.


CLASS ltc_roman_to_arabic IMPLEMENTATION.
  METHOD setup.
    mo_cut = NEW #( ).
  ENDMETHOD.


  METHOD convert_i.
    DATA(ld_result) = mo_cut->convert_roman_to_arabic( `I` ).

    cl_abap_unit_assert=>assert_equals( exp = 1
                                        act = ld_result ).
  ENDMETHOD.
ENDCLASS.

 

Über die Setup Methode können wir immer eine neue und saubere Instanz von MO_CUT (Class/Code under Test) erzeugen und müssen den Code nicht in jeder Testmethode implementieren. Entsprechend würden wir nun verschiedene Kombinationen und Sonderfälle testen. Ebenso sollten wir die Fehlerfälle abfangen und zu große Zahlen übergeben, bzw. auch einmal nicht übergeben.

CLASS ltc_roman_to_arabic DEFINITION FINAL
  FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.

  PRIVATE SECTION.
    DATA mo_cut TYPE REF TO zcl_bs_demo_roman_numbers.

    METHODS setup.

    METHODS convert_i         FOR TESTING RAISING cx_static_check.
    METHODS convert_iii       FOR TESTING RAISING cx_static_check.
    METHODS convert_iv        FOR TESTING RAISING cx_static_check.
    METHODS convert_v         FOR TESTING RAISING cx_static_check.
    METHODS convert_ix        FOR TESTING RAISING cx_static_check.
    METHODS convert_lxviii    FOR TESTING RAISING cx_static_check.
    METHODS convert_cccxlix   FOR TESTING RAISING cx_static_check.
    METHODS convert_mccxxxiv  FOR TESTING RAISING cx_static_check.
    METHODS convert_mmmcmxcix FOR TESTING RAISING cx_static_check.
    METHODS convert_unknown   FOR TESTING RAISING cx_static_check.
    METHODS convert_empty     FOR TESTING RAISING cx_static_check.
ENDCLASS.


CLASS ltc_roman_to_arabic IMPLEMENTATION.
  METHOD setup.
    mo_cut = NEW #( ).
  ENDMETHOD.


  METHOD convert_i.
    DATA(ld_result) = mo_cut->convert_roman_to_arabic( `I` ).

    cl_abap_unit_assert=>assert_equals( exp = 1
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_cccxlix.
    DATA(ld_result) = mo_cut->convert_roman_to_arabic( `CCCXLIX` ).

    cl_abap_unit_assert=>assert_equals( exp = 349
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_empty.
    TRY.
        mo_cut->convert_roman_to_arabic( `` ).

        cl_abap_unit_assert=>fail( 'Error should occure' ).

      CATCH cx_abap_not_in_allowlist.
    ENDTRY.
  ENDMETHOD.


  METHOD convert_iii.
    DATA(ld_result) = mo_cut->convert_roman_to_arabic( `III` ).

    cl_abap_unit_assert=>assert_equals( exp = 3
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_iv.
    DATA(ld_result) = mo_cut->convert_roman_to_arabic( `IV` ).

    cl_abap_unit_assert=>assert_equals( exp = 4
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_ix.
    DATA(ld_result) = mo_cut->convert_roman_to_arabic( `IX` ).

    cl_abap_unit_assert=>assert_equals( exp = 9
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_lxviii.
    DATA(ld_result) = mo_cut->convert_roman_to_arabic( `LXVIII` ).

    cl_abap_unit_assert=>assert_equals( exp = 68
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_mccxxxiv.
    DATA(ld_result) = mo_cut->convert_roman_to_arabic( `MCCXXXIV` ).

    cl_abap_unit_assert=>assert_equals( exp = 1234
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_mmmcmxcix.
    DATA(ld_result) = mo_cut->convert_roman_to_arabic( `MMMCMXCIX` ).

    cl_abap_unit_assert=>assert_equals( exp = 3999
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_unknown.
    TRY.
        mo_cut->convert_roman_to_arabic( `IIP` ).

        cl_abap_unit_assert=>fail( 'Error should occure' ).

      CATCH cx_abap_not_in_allowlist.
    ENDTRY.
  ENDMETHOD.


  METHOD convert_v.
    DATA(ld_result) = mo_cut->convert_roman_to_arabic( `V` ).

    cl_abap_unit_assert=>assert_equals( exp = 5
                                        act = ld_result ).
  ENDMETHOD.
ENDCLASS.


CLASS ltc_arabic_to_roman DEFINITION FINAL
  FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.

  PRIVATE SECTION.
    DATA mo_cut TYPE REF TO zcl_bs_demo_roman_numbers.

    METHODS setup.

    METHODS convert_0    FOR TESTING RAISING cx_static_check.
    METHODS convert_4000 FOR TESTING RAISING cx_static_check.
    METHODS convert_1    FOR TESTING RAISING cx_static_check.
    METHODS convert_3    FOR TESTING RAISING cx_static_check.
    METHODS convert_4    FOR TESTING RAISING cx_static_check.
    METHODS convert_9    FOR TESTING RAISING cx_static_check.
    METHODS convert_15   FOR TESTING RAISING cx_static_check.
    METHODS convert_587  FOR TESTING RAISING cx_static_check.
    METHODS convert_611  FOR TESTING RAISING cx_static_check.
    METHODS convert_789  FOR TESTING RAISING cx_static_check.
    METHODS convert_999  FOR TESTING RAISING cx_static_check.
    METHODS convert_1245 FOR TESTING RAISING cx_static_check.
    METHODS convert_3299 FOR TESTING RAISING cx_static_check.
    METHODS convert_3999 FOR TESTING RAISING cx_static_check.
ENDCLASS.


CLASS ltc_arabic_to_roman IMPLEMENTATION.
  METHOD setup.
    mo_cut = NEW #( ).
  ENDMETHOD.


  METHOD convert_0.
    TRY.
        mo_cut->convert_arabic_to_roman( 0 ).

        cl_abap_unit_assert=>fail( 'Error should occure' ).

      CATCH cx_abap_not_in_allowlist.
    ENDTRY.
  ENDMETHOD.


  METHOD convert_1.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 1 ).

    cl_abap_unit_assert=>assert_equals( exp = `I`
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_1245.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 1245 ).

    cl_abap_unit_assert=>assert_equals( exp = `MCCXLV`
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_15.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 15 ).

    cl_abap_unit_assert=>assert_equals( exp = `XV`
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_3.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 3 ).

    cl_abap_unit_assert=>assert_equals( exp = `III`
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_3299.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 3299 ).

    cl_abap_unit_assert=>assert_equals( exp = `MMMCCXCIX`
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_3999.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 3999 ).

    cl_abap_unit_assert=>assert_equals( exp = `MMMCMXCIX`
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_4.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 4 ).

    cl_abap_unit_assert=>assert_equals( exp = `IV`
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_4000.
    TRY.
        mo_cut->convert_arabic_to_roman( 4000 ).

        cl_abap_unit_assert=>fail( 'Error should occure' ).

      CATCH cx_abap_not_in_allowlist.
    ENDTRY.
  ENDMETHOD.


  METHOD convert_587.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 587 ).

    cl_abap_unit_assert=>assert_equals( exp = `DLXXXVII`
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_611.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 611 ).

    cl_abap_unit_assert=>assert_equals( exp = `DCXI`
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_789.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 789 ).

    cl_abap_unit_assert=>assert_equals( exp = `DCCLXXXIX`
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_9.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 9 ).

    cl_abap_unit_assert=>assert_equals( exp = `IX`
                                        act = ld_result ).
  ENDMETHOD.


  METHOD convert_999.
    DATA(ld_result) = mo_cut->convert_arabic_to_roman( 999 ).

    cl_abap_unit_assert=>assert_equals( exp = `CMXCIX`
                                        act = ld_result ).
  ENDMETHOD.
ENDCLASS.

 

Führen wir nun einmal die Testklasse aus, dabei sollten alle Testfälle auf Rot laufen, da wir noch keine Logik hinterlegt haben. Wie zu erwarten haben wir nun sehr viele Unit Tests, die erst einmal auf Rot laufen.

 

Mapping

Im ersten Schritt benötigen wir ein Mapping der Zahlen auf die Zeichen und zurück. Dazu legen wir uns erst einmal eine interne Tabelle an, die wir dann befüllen. Für den direkten Zugriff legen wir sekundäre Schlüssel an, damit haben wir einen schnellen Einzelsatzzugriff, lassen die Tabelle als Standard, um später eine einfachere Verarbeitung per LOOP machen zu können.

TYPES: BEGIN OF ts_mapping,
         roman  TYPE string,
         arabic TYPE i,
       END OF ts_mapping.
       
TYPES tt_mapping TYPE STANDARD TABLE OF ts_mapping WITH EMPTY KEY
 WITH UNIQUE SORTED KEY by_roman COMPONENTS roman
 WITH UNIQUE SORTED KEY by_arabic COMPONENTS arabic.

 

Damit die Daten zum Start befüllt sind, implementieren wir den Konstruktor in der Klasse und befüllen dort die Daten, die wir als Member Attribut in der Klasse speichern.

mt_mapping = VALUE #( ( roman = `I` arabic = 1 )
                      ( roman = `V` arabic = 5 )
                      ( roman = `X` arabic = 10 )
                      ( roman = `L` arabic = 50 )
                      ( roman = `C` arabic = 100 )
                      ( roman = `D` arabic = 500 )
                      ( roman = `M` arabic = 1000 ) ).

 

Die Anlage des Konstruktors kannst du auch ganz einfach per STRG + 1 auf den Klassennamen machen. Hier bekommst du Vorschläge für gängige Implementierungen.

 

Römisch nach Arabisch

Beginnen wir mit dem leichtesten Anwendungsfall, der Umrechnung der römischen Zahlen in die Arabischen. Beginnen wir damit die Zahlen zu verarbeiten. Dazu gehen wir Zeichen per Zeichen durch den String und lesen uns den aktuellen Wert ein, diesen Addieren wir auf unser Ergebnis. Finden wir das Zeichen nicht, weil es dieses nicht gibt, lösen wir eine Ausnahme aus.

DO strlen( id_roman ) TIMES.
  DATA(ld_actual_roman) = substring( val = id_roman
                                     off = sy-index - 1
                                     len = 1 ).

  TRY.
      DATA(ld_actual_value) = mt_mapping[ KEY by_roman COMPONENTS roman = ld_actual_roman ]-arabic.
    CATCH cx_sy_itab_line_not_found.
      RAISE EXCEPTION NEW cx_abap_not_in_allowlist( ).
  ENDTRY.

  rd_result += ld_actual_value.
ENDDO.

 

Nun können wir unsere Unit Tests ausführen, um zu prüfen, ob die Logik bereits funktioniert. Wie du siehst, decken wir bereits einen Sonderfall ab und auch die direkten Zeichen funktionieren.

 

Hier scheint es noch ein Problem mit der Subtraktionsregel zu geben, da wir diese nicht berücksichtigen. Dazu speichern wir uns den letzten Wert der Ermittlung und prüfen, ob der letzte Wert größer oder gleich war wie der aktuelle Wert. Ansonsten müssen wir den letzten Wert noch einmal abziehen und den aktuellen Wert minus den letzten Wert addieren. Die Logik dazu würde wie folgt aussehen.

IF ld_last_value >= ld_actual_value.
  rd_result += ld_actual_value.
ELSE.
  rd_result = rd_result - ld_last_value + ld_actual_value - ld_last_value.
ENDIF.

ld_last_value = ld_actual_value.

 

Führen wir nun unsere Unit Tests noch einmal aus, sind wir fast so weit durch und müssen nur noch einen Sonderfall abfangen, wenn ein leerer String übergeben wird.

 

Dazu können wir am Anfang der Methode eine einfache Prüfung implementieren und verlassen dann per Exception die Methode wieder.

IF id_roman IS INITIAL.
  RAISE EXCEPTION NEW cx_abap_not_in_allowlist( ).
ENDIF.

 

Zum Abschluss führen wir noch einmal den Unit Test aus und unsere Methode funktioniert so weit nun.

 

Arabisch nach Römisch

Nun geht es um die Implementierung der zweiten Methode, um eine arabische Zahl in eine Römische Zahl zu konvertieren. Fangen wir in diesem Fall mit einem Sonderfall ab und gleichen die Zahlen ab, die außerhalb des Regelbereichs liegen.

IF id_arabic < 1 OR id_arabic > 3999.
  RAISE EXCEPTION NEW cx_abap_not_in_allowlist( ).
ENDIF.

 

Führen wir die Unit Tests aus, erhalten wir die erste Rückmeldung zu diesen Fällen. Damit wären die Sonderfälle schon einmal abgedeckt.

 

Im nächsten Schritt erzeugen wir die römischen Zahlen, dabei beginnen wir mit der größten Zahl. Dazu arbeiten wir mit dem Step -1, um über die interne Tabelle rückwärtszulaufen und damit bei der größten Zahl zu beginnen. Im nächsten Schritt berechnen wir, wie oft die aktuelle Zahl in den offenen Wert passt und erzeugen schon einen Teil der Zahl. Am Ende der Schleife ziehen wir die Gesamtzahl vom offenen Betrag ab und hängen das Ergebnis an.

DATA(ld_open) = id_arabic.

LOOP AT mt_mapping INTO DATA(ls_mapping) STEP -1.
  DATA(ld_actual_value) = 0.
  DATA(ld_part) = ``.

  WHILE ( ld_open - ld_actual_value ) >= ls_mapping-arabic.
    ld_actual_value += ls_mapping-arabic.
    ld_part &&= ls_mapping-roman.
  ENDWHILE.

  ld_open -= ld_actual_value.
  rd_result &&= ld_part.
ENDLOOP.

 

Führen wir die Unit Tests aus, erhalten wir mehr positive Ergebnisse, allerdings scheint die Logik noch nicht vollständig zu sein. Bei näherer Prüfung wird dir auffallen, dass wir noch mehr als drei Mal das gleiche Zeichen erzeugen (siehe Regel) und auch die Sonderregel mit mehr als einem Zwischenzeichen verletzen.

 

Dazu müssen wir nun das Customizing erweitern, um weitere Informationen zu den Zeichen zu bekommen. Welches das höhere Zeichen ist und welches für die Subtraktionsregel benötigt wird. Entsprechend sieht nun das Customizing wie folgt aus:

TYPES: BEGIN OF ts_mapping,
         roman  TYPE string,
         arabic TYPE i,
         low    TYPE string,
         high   TYPE string,
         single TYPE abap_boolean,
       END OF ts_mapping.
       
mt_mapping = VALUE #( ( roman = `I` arabic = 1 low = `I` high = `V` )
                      ( roman = `V` arabic = 5 low = `I` high = `X` single = abap_true )
                      ( roman = `X` arabic = 10 low = `X` high = `L` )
                      ( roman = `L` arabic = 50 low = `X` high = `C` single = abap_true )
                      ( roman = `C` arabic = 100 low = `C` high = `D` )
                      ( roman = `D` arabic = 500 low = `C` high = `M` single = abap_true )
                      ( roman = `M` arabic = 1000 low = `C` high = `M` ) ).    

 

Nun fügen wir noch die Logik mit den zwei Sonderregeln ein. Dazu lesen wir für das aktuelle Mapping den entsprechenden hohen und niedrigen Wert. Im Anschluss gleichen wir die beiden Regeln ab:

  • Darf dieses Zeichen nur einmal vorhanden sein
  • Werden 4 oder mehr gleiche Zeichen verwendet

 

DATA(ls_low) = mt_mapping[ KEY by_roman COMPONENTS roman = ls_mapping-low ].
DATA(ls_high) = mt_mapping[ KEY by_roman COMPONENTS roman = ls_mapping-high ].

IF ( ls_mapping-single = abap_true AND ( ls_high-arabic - ls_low-arabic ) <= ld_open ) 
 OR strlen( ld_part ) > 3.
  ld_part = ls_low-roman && ls_high-roman.
  ld_actual_value = ls_high-arabic - ls_low-arabic.
ENDIF.

 

Führen wir das nun zum Abschluss unseren Unit Test durch, sind alle Fälle auf Grün, die Logik wurde vollständig implementiert.

 

Komplettes Beispiel

Die vollständige Implementierung des Konverters sieht nun wie folgt aus, wobei du die komplette Testklasse im oberen Bereich findest.

CLASS zcl_bs_demo_roman_numbers DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS constructor.

    METHODS convert_roman_to_arabic
      IMPORTING id_roman         TYPE string
      RETURNING VALUE(rd_result) TYPE i
      RAISING   cx_abap_not_in_allowlist.

    METHODS convert_arabic_to_roman
      IMPORTING id_arabic        TYPE i
      RETURNING VALUE(rd_result) TYPE string
      RAISING   cx_abap_not_in_allowlist.

  PRIVATE SECTION.
    TYPES: BEGIN OF ts_mapping,
             roman  TYPE string,
             arabic TYPE i,
             low    TYPE string,
             high   TYPE string,
             single TYPE abap_boolean,
           END OF ts_mapping.
    TYPES tt_mapping TYPE STANDARD TABLE OF ts_mapping WITH EMPTY KEY
     WITH UNIQUE SORTED KEY by_roman COMPONENTS roman
     WITH UNIQUE SORTED KEY by_arabic COMPONENTS arabic.

    DATA mt_mapping TYPE tt_mapping.
ENDCLASS.


CLASS zcl_bs_demo_roman_numbers IMPLEMENTATION.
  METHOD constructor.
    mt_mapping = VALUE #( ( roman = `I` arabic = 1 low = `I` high = `V` )
                          ( roman = `V` arabic = 5 low = `I` high = `X` single = abap_true )
                          ( roman = `X` arabic = 10 low = `X` high = `L` )
                          ( roman = `L` arabic = 50 low = `X` high = `C` single = abap_true )
                          ( roman = `C` arabic = 100 low = `C` high = `D` )
                          ( roman = `D` arabic = 500 low = `C` high = `M` single = abap_true )
                          ( roman = `M` arabic = 1000 low = `C` high = `M` ) ).
  ENDMETHOD.


  METHOD convert_arabic_to_roman.
    IF id_arabic < 1 OR id_arabic > 3999.
      RAISE EXCEPTION NEW cx_abap_not_in_allowlist( ).
    ENDIF.

    DATA(ld_open) = id_arabic.

    LOOP AT mt_mapping INTO DATA(ls_mapping) STEP -1.
      DATA(ld_actual_value) = 0.
      DATA(ld_part) = ``.

      WHILE ( ld_open - ld_actual_value ) >= ls_mapping-arabic.
        ld_actual_value += ls_mapping-arabic.
        ld_part &&= ls_mapping-roman.
      ENDWHILE.

      DATA(ls_low) = mt_mapping[ KEY by_roman COMPONENTS roman = ls_mapping-low ].
      DATA(ls_high) = mt_mapping[ KEY by_roman COMPONENTS roman = ls_mapping-high ].

      IF ( ls_mapping-single = abap_true AND ( ls_high-arabic - ls_low-arabic ) <= ld_open ) OR strlen( ld_part ) > 3.
        ld_part = ls_low-roman && ls_high-roman.
        ld_actual_value = ls_high-arabic - ls_low-arabic.
      ENDIF.

      ld_open -= ld_actual_value.
      rd_result &&= ld_part.
    ENDLOOP.
  ENDMETHOD.


  METHOD convert_roman_to_arabic.
    IF id_roman IS INITIAL.
      RAISE EXCEPTION NEW cx_abap_not_in_allowlist( ).
    ENDIF.

    DATA(ld_last_value) = 0.

    DO strlen( id_roman ) TIMES.
      DATA(ld_actual_roman) = substring( val = id_roman
                                         off = sy-index - 1
                                         len = 1 ).

      TRY.
          DATA(ld_actual_value) = mt_mapping[ KEY by_roman COMPONENTS roman = ld_actual_roman ]-arabic.
        CATCH cx_sy_itab_line_not_found.
          RAISE EXCEPTION NEW cx_abap_not_in_allowlist( ).
      ENDTRY.

      IF ld_last_value >= ld_actual_value.
        rd_result += ld_actual_value.
      ELSE.
        rd_result = rd_result - ld_last_value + ld_actual_value - ld_last_value.
      ENDIF.

      ld_last_value = ld_actual_value.
    ENDDO.
  ENDMETHOD.
ENDCLASS.

 

Zusammenfassung

Bei der Übung handelt es sich nur um einen kleinen praktischen Einblick in das Thema Test Driven Development (TDD) und nicht um die vollständige Theorie.

 

Vorteile

Allerdings können wir direkt ein paar Vorteile aus der Anwendung mitnehmen:

  • Wir haben uns Gedanken über die Testfälle und selbst die Ausnahmen gemacht.
  • Wir haben nur so viel Quellcode erzeugt, wie auch für die Anwendung und die Tests nötig war.
  • Während der Entwicklung waren die Unit Tests ein Sicherheitsnetz, welches uns schnell Feedback zum aktuellen Stand der Entwicklung gegeben hat.
  • Unsere Anwendung ist nach Abschluss direkt mit Testfällen ausgestattet.
  • Wir haben eine hohe Abdeckung des Quellcodes erreicht.

 

 

Nachteile

Neben den vielen Vorteilen solltest du auch die Nachteile beleuchten:

  • Viel Disziplin und Zeit bei der Konzeptionierung, bevor die eigentliche Entwicklung der Lösung durchgeführt wird.

 

Fazit

Test Driven Development erfordert ein hohes Maß an Zurückhaltung von dir als Entwickler, um nicht direkt mit der Implementierung der Logik zu beginnen, sondern erst den eigentlichen Rumpf zur Verfügung zu stellen. Dabei machst du dir Gedanken über die Testbarkeit und wirst mit implementierten Unit Tests belohnt. Du hast eine andere Lösung? Dann ab in die Kommentare damit.


Enthaltene Themen:
TippABAP in der PraxisTDDTest Driven Development
Kommentare (1)



Und weiter ...

Bist du zufrieden mit dem Inhalt des Artikels? Wir posten jeden Freitag neuen Content im Bereich ABAP und unregelmäßig in allen anderen Bereichen. Schaue bei unseren Tools und Apps vorbei, diese stellen wir kostenlos zur Verfügung.


ABAP in Praxis - String Verarbeitung

Kategorie - ABAP

In diesem praktischen Beispiel schauen wir uns die String Verarbeitung zur Ermittlung der CDS Namen in CamelCase an und wie du das mit ABAP umsetzen kannst.

15.10.2024

ABAP in der Praxis - Datenmenge zusammenführen

Kategorie - ABAP

Wir führen wir zwei unterschiedliche Datenmengen in ABAP zusammen, vor allem im Hinblick auf das Moderne ABAP? Eine praktische Aufgabe zum Thema.

17.09.2024

ABAP in der Praxis - Modern ABAP

Kategorie - ABAP

In dieser kleinen Aufgabe schauen wir uns bestehenden klassischen ABAP Quellcode an und versuchen diesen nach Modern ABAP zu optimieren.

27.08.2024

ABAP Tipp - Performance Datenfilterung

Kategorie - ABAP

Welche Anweisung verwendest du in ABAP zur Filterung von internen Tabellen und ist diese performant? In diesem Artikel mehr dazu.

13.08.2024

ABAP in der Praxis - Typkonvertierung

Kategorie - ABAP

Wie würdest du diese Typkonvertierung in ABAP durchführen? Ein Beispiel aus der Praxis und ein Lösungsvorschlag.

16.07.2024