ABAP Unit - Legacy objects
This article is about so-called legacy code and how you can use ABAP Unit there too. However, we can only recommend this type to a limited extent.
Table of contents
The term legacy object sounds a bit derogatory when it comes to reports and function modules, but it is also about the possibilities that you have during the test. More on this in this article.
Report
Do you still have an old report in the system that is often revised by you and you therefore want to implement a few tests so that future developments can be implemented in a stable manner? Implementing a test class in a report is not a problem, but you should keep two important points in mind:
- Spaghetti code cannot be tested because it does not provide any callable subroutines
- Forms or methods should have proper interfaces and be based on as little global data as possible
So if you have already implemented clean subroutines (methods or forms), it is not a problem to implement a test class. Two examples of a report that do the same in terms of content. Company code data is read, recorded in a table and then output using CL_DEMO_OUTPUT.
The first example with an implementation as a local class and a test class:
REPORT ztest_report_with_class.
*** Selection screen
PARAMETERS:
p_bukrs TYPE t001-bukrs.
*** Report logic
CLASS lcl_prog DEFINITION FINAL.
PUBLIC SECTION.
METHODS:
main,
select_company_code
IMPORTING
id_bukrs TYPE bukrs
RETURNING VALUE(rs_company_data) TYPE t001.
ENDCLASS.
CLASS lcl_prog IMPLEMENTATION.
METHOD main.
DATA:
lt_company_data TYPE STANDARD TABLE OF t001.
INSERT select_company_code( p_bukrs ) INTO TABLE lt_company_data.
cl_demo_output=>display_data( lt_company_data ).
ENDMETHOD.
METHOD select_company_code.
SELECT SINGLE *
FROM t001
WHERE bukrs = @id_bukrs
INTO @rs_company_data.
ENDMETHOD.
ENDCLASS.
*** Local testclass
CLASS ltc_prog DEFINITION FINAL FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS:
select_existing_company_code FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS ltc_prog IMPLEMENTATION.
METHOD select_existing_company_code.
DATA(lo_cut) = NEW lcl_prog( ).
DATA(ls_found) = lo_cut->select_company_code( '4711' ).
cl_abap_unit_assert=>assert_not_initial( ls_found ).
ENDMETHOD.
ENDCLASS.
*** Report
START-OF-SELECTION.
NEW lcl_prog( )->main( ).
The second example with classic FORM routines and a test class:
REPORT ztest_report_with_forms.
*** Selection screen
PARAMETERS:
p_bukrs TYPE t001-bukrs.
*** Report
START-OF-SELECTION.
PERFORM main.
*** Report logic
FORM main.
DATA:
lt_company_data TYPE STANDARD TABLE OF t001,
ls_company_data TYPE t001.
PERFORM select_company_code
USING p_bukrs
CHANGING ls_company_data.
INSERT ls_company_data INTO TABLE lt_company_data.
cl_demo_output=>display_data( lt_company_data ).
ENDFORM.
FORM select_company_code USING p_bukrs TYPE t001-bukrs
CHANGING cs_company_code TYPE t001.
SELECT SINGLE *
FROM t001
WHERE bukrs = @p_bukrs
INTO @cs_company_code.
ENDFORM.
*** Local testclass
CLASS ltc_prog DEFINITION FINAL FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS:
select_existing_company_code FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS ltc_prog IMPLEMENTATION.
METHOD select_existing_company_code.
DATA:
ls_found TYPE t001.
PERFORM select_company_code
USING '4711'
CHANGING ls_found.
cl_abap_unit_assert=>assert_not_initial( ls_found ).
ENDMETHOD.
ENDCLASS.
Function module
Function modules can be tested by you in the same way and are almost even better suited than a report. This is due to the encapsulation of the data and the interface that a function module brings with it from the start. If you look into the main include of a function group, you will already find many includes commented out that are intended for different things.
The include with the ending T99 can be used to test the function modules of the function group. To do this, we create a simple function module in the system that is supposed to carry out a simple calculation for us.
FUNCTION z_bs_demo_calculate_two_number
IMPORTING
VALUE(id_number_one) TYPE i
VALUE(id_number_two) TYPE i
EXPORTING
VALUE(ed_result) TYPE i.
ed_result = id_number_one + id_number_two.
ENDFUNCTION.
Then we create the include and implement the test class, which then calls the function module. In this case we have only created one test method as an example.
CLASS ltc_function_modules DEFINITION FINAL FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS:
calculate_1_and_5 FOR TESTING.
ENDCLASS.
CLASS ltc_function_modules IMPLEMENTATION.
METHOD calculate_1_and_5.
DATA:
ld_result TYPE i.
CALL FUNCTION 'Z_BS_DEMO_CALCULATE_TWO_NUMBER'
EXPORTING
id_number_one = 1
id_number_two = 5
IMPORTING
ed_result = ld_result.
cl_abap_unit_assert=>assert_equals( act = ld_result exp = 6 ).
ENDMETHOD.
ENDCLASS.
This makes it easy to equip the function modules, classes and forms within a function group with tests and to test them with every change.
Restrictions
There are still small restrictions for “legacy code”, because not all of the techniques that we deal with in this book can be used. For example, no SEAMs are available.
A little tip from our side for you is therefore to carry out all developments in global classes and only use function modules and reports as covers for the call, so that the majority of the logic is neatly wrapped in the OO and you can enjoy full efficiency with ABAP Unit. We will tell you more about this in the architecture article of this series.
Conclusion
You can also write ABAP unit tests for old objects or legacy code, but the code must meet certain requirements for this to be possible. But the more unit tests you have in the system and thus a high level of coverage, the more likely it is that you will notice problems with adjustments.