ABAP in der Praxis - Test Driven Development
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.
Inhaltsverzeichnis
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.