This is a test message to test the length of the message box.
Login
ABAP Unit Testable Code
Created by Software-Heroes

ABAP Unit - Testable Code (Part 1)

705

In this article, we'll look at how you can cleanly implement new functions in older code and then test it.



What does writing testable code mean? In this chapter we want to go into some methods and how you can modify or expand your existing coding accordingly so that it can be tested.

 

General

In general, we speak of testable code if it can be checked automatically with ABAP unit tests. So there is a clean interface to a method or a subroutine and there are as few dependencies as possible on global data, i.e. a clean encapsulation of the code.

 

Island of Happiness

Methodology

Describes a method for extending legacy code by adding logic or functionality. In the following example we have legacy code that should be expanded with new functionality. In such a case, the new logic can be developed as a separate class, decoupled from the main code. The relevant data and logics are transferred via methods of the new class. The new logic is thus encapsulated in a global class and can be tested automatically with ABAP unit tests. All of our new logic can be neatly developed and encapsulated and we hardly need to adapt the existing code.

 

Example

The report was developed classically without FORM routines and still contains a lot of global variables and interdependencies. Our new implementation is intended to add new functions to the report, in which we want to enrich the output data with additional geodata. We would like to provide unit tests for the new functionality.

REPORT z_test_ioh.

*----------------------------------------------------------------------*
*--- Globale data
*----------------------------------------------------------------------*
TABLES:
  t001.

TYPES:
  BEGIN OF ts_output,
    bukrs      TYPE t001-bukrs,
    butxt      TYPE t001-butxt,
    name1      TYPE adrc-name1,
    name2      TYPE adrc-name2,
    city1      TYPE adrc-city1,
    addr_group TYPE adrc-addr_group,
  END OF ts_output,
  tt_output TYPE STANDARD TABLE OF ts_output WITH EMPTY KEY.

DATA:
  gt_company_code TYPE SORTED TABLE OF t001 WITH UNIQUE KEY bukrs,
  gs_company_code TYPE t001,
  gs_address      TYPE adrc,
  gt_output_table TYPE tt_output,
  gs_output_table TYPE ts_output,
  gs_vari         TYPE disvariant,
  go_alv          TYPE REF TO cl_salv_table.

*----------------------------------------------------------------------*
*--- Selection screen
*----------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK b01.
  SELECT-OPTIONS:
    s_bukrs FOR t001-bukrs,
    s_waers FOR t001-waers.
  PARAMETERS:
    p_vari TYPE slis_vari DEFAULT '/DEFAULT',
    p_test AS CHECKBOX DEFAULT abap_true.
SELECTION-SCREEN END OF BLOCK b01.

*----------------------------------------------------------------------*
*--- Events
*----------------------------------------------------------------------*
INITIALIZATION.
  AUTHORITY-CHECK OBJECT 'S_TCODE'
   ID 'TCD' FIELD 'Z60DUMMY_TCODE'.
  IF sy-subrc <> 0.
    MESSAGE e000(z60bc) WITH 'No authority for transaction' 'Z60DUMMY_TCODE'.
  ENDIF.


AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_vari.
  gs_vari-report = sy-repid.
  gs_vari-username = sy-uname.
  gs_vari-handle = 'HDL'.

  CALL FUNCTION 'REUSE_ALV_VARIANT_F4'
    EXPORTING
      is_variant = gs_vari
      i_save     = 'A'
    IMPORTING
      es_variant = gs_vari
    EXCEPTIONS
      OTHERS     = 1.

  IF sy-subrc = 0.
    p_vari = gs_vari-variant.
  ENDIF.


START-OF-SELECTION.
  CLEAR: gt_company_code, gt_output_table.

  SELECT *
    FROM t001
    WHERE bukrs IN @s_bukrs
      AND waers IN @s_waers
    INTO TABLE @gt_company_code.

  LOOP AT gt_company_code INTO gs_company_code.
    CLEAR: gs_output_table.
    gs_output_table = CORRESPONDING #( gs_company_code ).

    SELECT SINGLE *
      FROM adrc
      WHERE addrnumber = @gs_company_code-adrnr
      INTO @gs_address.

    IF sy-subrc = 0.
      gs_output_table = CORRESPONDING #( BASE ( gs_output_table ) gs_address ).
    ENDIF.

    INSERT gs_output_table INTO TABLE gt_output_table.
  ENDLOOP.

  cl_salv_table=>factory(
   IMPORTING r_salv_table = go_alv
   CHANGING t_table = gt_output_table ).

  DATA(lo_func) = go_alv->get_functions( ).
  lo_func->set_all( ).

  DATA(lo_disp) = go_alv->get_display_settings( ).
  lo_disp->set_striped_pattern( abap_true ).

  lo_disp->set_list_header( 'Found data' ).

  DATA(lo_lay) = go_alv->get_layout( ).
  lo_lay->set_default( abap_true ).
  lo_lay->set_key( VALUE #( report = sy-repid handle = 'HDL' ) ).
  lo_lay->set_save_restriction( ).

  IF p_vari IS NOT INITIAL.
    lo_lay->set_initial_layout( p_vari ).
  ENDIF.

  go_alv->display( ).

 

We implement the new function in a global class. Accordingly, we provide an interface that provides the interface to the report. Furthermore, we need the output structure for the interface to the methods, which we then have to announce accordingly. We adopt the structure in the interface and replace the definition in the report. Our interface then looks like this:

INTERFACE zif_test_ioh PUBLIC.
  TYPES:
    td_geometry_data TYPE p LENGTH 15 DECIMALS 8,

    BEGIN OF ts_output,
      bukrs      TYPE t001-bukrs,
      butxt      TYPE t001-butxt,
      name1      TYPE adrc-name1,
      name2      TYPE adrc-name2,
      city1      TYPE adrc-city1,
      addr_group TYPE adrc-addr_group,
      " New Fields
      latitude   TYPE td_geometry_data,
      longitude  TYPE td_geometry_data,
    END OF ts_output,
    tt_output TYPE STANDARD TABLE OF ts_output WITH EMPTY KEY,

    BEGIN OF ts_geometry,
      latitude  TYPE td_geometry_data,
      longitude TYPE td_geometry_data,
    END OF ts_geometry.

  METHODS:
    enrich_data
      CHANGING
        cs_output TYPE ts_output.
ENDINTERFACE.

 

The report is now enriched with our new logic in the appropriate places in order to work with minimal changes.

  • Change of the declaration part

 

  • Instantiation of the object

 

  • Calling the additional logic

 

At the end, we still implement the logic in the class and ABAP unit tests in order to make our written code automatically testable and to be able to carry out future extensions more easily. You can find the example implementation of the class in the examples at the end of the book.

 

Final class

You can find the example implementation of the class here at this point, so that you can encapsulate the new functions neatly:

CLASS zcl_test_ioh DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES zif_test_ioh.

  PROTECTED SECTION.
  PRIVATE SECTION.
    METHODS:
      get_geolocation_for_city
        IMPORTING
                  id_city            TYPE adrc-city1
        RETURNING VALUE(rs_geometry) TYPE zif_60bs_test_ioh=>ts_geometry.
ENDCLASS.

CLASS zcl_test_ioh IMPLEMENTATION.
  METHOD zif_60bs_test_ioh~enrich_data.
    DATA(ls_geometry) = get_geolocation_for_city( cs_output-city1 ).
    IF ls_geometry IS INITIAL.
      RETURN.
    ENDIF.

    cs_output-latitude = ls_geometry-latitude.
    cs_output-longitude = ls_geometry-longitude.
  ENDMETHOD.


  METHOD get_geolocation_for_city.
    " Call geolocation api
  ENDMETHOD.
ENDCLASS.

 

Conclusion

You can see from our example that even in old code, new code can be implemented cleanly. You can then cleanly test and validate these new functions with ABAP Unit.


Included topics:
ABAP UnitABAPUnit TestsTestbarer CodeIsland of Hapiness
Comments (0)



And further ...

Are you satisfied with the content of the article? We post new content in the ABAP area every Friday and irregularly in all other areas. Take a look at our tools and apps, we provide them free of charge.


ABAP Unit - Test execution

Category - ABAP

What options are there for executing ABAP Unit and what hidden functions might you not know about? More details about the different modes in this article.

06/25/2024

ABAP Unit - Automation

Category - ABAP

When do objects break during development and what side effects can adjustments have? In this article we take a closer look at the topic.

01/05/2024

ABAP Unit - TDF (Function Double)

Category - ABAP

In this article, let's take a look at how we can deal with function modules as dependencies without testing them as well.

04/07/2023

RAP - ABAP Unit (Service)

Category - ABAP

How do you actually test the service of an interface or RAP app automatically? In this article we look at unit tests for OData services.

01/20/2023

RAP - ABAP Unit (Business Object)

Category - ABAP

At this point, we'll take a look at how we can test our RAP Business Object automatically in order to check all important functions at the touch of a button.

01/13/2023