Wednesday, 18 November 2020

Debugger-Script to generate Value-Statements for arbitrary Structures/Tables (Idea and Snipptes)

As I am forcing myself to write more Unit-Tests for my development classes, I was struggling a bit to set up the test-scenarios since the needed value statements may become big and boring to type in.

I know in ADT is a tool included in the SQL-Console to translate the select-output to a file including the value statements, but still, it took me some effort because not any table can be constructed via sql select (or the type of the target structure/table does not correspond to the db-table type…).

So I thought the Debugger might offer some help by implementing a little script to derive the statement from the actual variable (while debugging). I was playing around with the cl_tpda_script* classes to “deconstruct” an entity into its components and recursively build a tree-like structure as the base for the corresponding value statement.

General Doing

As you know, a debugger script can be executed after reaching a break-point. My idea was to call a selection-screen in order to type in the variable name which should be converted after starting the Script.

This would somehow look like this:

SAP ABAP Tutorial and Material, SAP ABAP Certification, SAP ABAP Exam Prep, SAP ABAP Guides
Debugger Script Start

This should be enough input for building the corresponding value statement. I thought it would be nice to see the structure of the statement beside the actual content. Therefore I used a splitter control with an alv_tree on the left side and a text_control on the right side.

SAP ABAP Tutorial and Material, SAP ABAP Certification, SAP ABAP Exam Prep, SAP ABAP Guides
Value-Statement Output + Structure of Object

The output would look like this after reading the flight connections.

SAP ABAP Tutorial and Material, SAP ABAP Certification, SAP ABAP Exam Prep, SAP ABAP Guides
With flight connections

Code-Snippets


My Idea was to use an abstract base class to model a tree structure. (Sry for the naming, but it is just a protoype :))

ZTEST_ENTITY                           "-> Abstract Base-Class
\__ZTEST_SIMPLE_ENTITY                 "-> No-Child Elements
   \__ZTEST_REF_ENTITY
\__ZTEST_COMPLEX_ENTITY                "-> Child-Elements: DATA: mt_components TYPE TABLE OF...
   \__ZTEST_STRUCT_ENTITY              "-> Structured Entity
      \_ZTEST_TABLE_LINE_ENTITY
   \__ZTEST_TABLE_ENTITY               "-> Table Entity

ZTEST_ENTITY defines some abstract methods in order to have a flexible model with an interface.

  PROTECTED SECTION.
    DATA: mv_key          TYPE string,
          mv_contains_ref TYPE abap_bool.
...
  METHODS:
      "! Returning the Content (Including subnodes, if existent)
      "! @parameter rt_content | String-Table with Node-Content
      get_content ABSTRACT RETURNING VALUE(rt_content) TYPE ztest_print_value=>tty_string,
      "! Returning a flag if reference Objects are contained (Including subnodes)
      "! @parameter rv_contains_ref | Flag, if reference is contained
      contains_reference ABSTRACT RETURNING VALUE(rv_contains_ref) TYPE abap_bool,
      "! Returning a Description of the Node-Type
      "! @parameter rv_node_type | Description of the Node-Type
      get_node_type ABSTRACT RETURNING VALUE(rv_node_type) TYPE string,
      "! This Method must be implemented by inheriting class to register itself in the alv_tree
      "! @parameter io_salv_tree | Reference to salv_tree Object
      "! @parameter iv_parent_key | Parent-Key in the salv_tree
      "! @parameter io_ref_node_table | Reference to Node-Table (Used for GUI-Functionalities)
      "! @raising cx_salv_msg | Salv-Error
      add_to_node ABSTRACT IMPORTING io_salv_tree      TYPE REF TO cl_salv_tree
                                     iv_parent_key     TYPE salv_de_node_key
                                     io_ref_node_table TYPE REF TO tty_node_table OPTIONAL
                           RAISING   cx_salv_msg.

Non-Scalar Entities must be derived from ztest_complex_entity since this class contains a member holding its child-elements.

  PROTECTED SECTION.
    DATA: mt_components TYPE TABLE OF REF TO ztest_entity.

The Key-Value Pairs containing the Payload are implemented in the subclasses of ztest_simple_entity.

  PROTECTED SECTION.
    DATA: mv_simple_value TYPE string.

As mentioned before, the serzialization is done with help of the cl_tpda_script*-classes.

Strucutre Level:

The class cl_tpda_script_structdescr is offering a method components( ) to loop through any field of the structure. The type of the component can be derived via down-cast of the field TPDA_SCR_STRUCT_COMP-SYMBQUICK-QUICKDATA:

* Payload (Quickdata) (Used for Downcasts)
    DATA: lr_symbsimple TYPE REF TO tpda_sys_symbsimple,
          lr_symbstruct TYPE REF TO tpda_sys_symbstruct,
          lr_symbstring TYPE REF TO tpda_sys_symbstring,
          lr_symbref    TYPE REF TO tpda_sys_symbdatref,
          lr_symbobjref TYPE REF TO tpda_sys_symbobjref,
          lr_symbtab    TYPE REF TO tpda_sys_symbtab.

The the constructor of class ztest_struct_entity would look like this (some lines are omitted)

* Importing Refernce Must be Struct-Type    
    lo_struct_descr ?= io_data_descr.

    lo_struct_descr->components(
      IMPORTING
        p_components_full_it = lt_components_full_it                " TPDA: Retrieval-Tabelle für get_Symb_Struct_1stLevel
        p_components_it = lt_components_it
    ).

* Loop through all components of the structure and build child Elements
    LOOP AT lt_components_full_it ASSIGNING FIELD-SYMBOL(<comp>).
      ASSIGN <comp>-symbquick-quickdata TO FIELD-SYMBOL(<quick_data>).
      TRY.
          lr_symbsimple ?= <quick_data>.
          mt_components = VALUE #( BASE mt_components
            ( NEW ztest_simple_struct(
                iv_key = <comp>-compname
                iv_value = lr_symbsimple->valstring
              )
            )
          ).
        CATCH cx_sy_move_cast_error.
          TRY.
              lr_symbstring ?= <quick_data>.
              mt_components = VALUE #( BASE mt_components
                ( NEW ztest_simple_struct(
                    iv_key = <comp>-compname
                    iv_value = lr_symbstring->valstring
                  )
                )
              ).
            CATCH cx_sy_move_cast_error.
              TRY.
                  lr_symbstruct ?= <quick_data>.
                  lo_comp_descr = cl_tpda_script_data_descr=>factory( p_var_name = lt_components_it[ compname = <comp>-compname ]-longname ).
                  mt_components = VALUE #( BASE mt_components
                    ( NEW ztest_struct_entity(
                        iv_compname = <comp>-compname
                        io_data_descr = lo_comp_descr
                        iv_is_root = abap_false
                      )
                    )
                  ).
                CATCH cx_sy_move_cast_error.
                  TRY.
                      lr_symbtab ?= <quick_data>.
                      lo_comp_descr = cl_tpda_script_data_descr=>factory( p_var_name = lt_components_it[ compname = <comp>-compname ]-longname ).
                      mt_components = VALUE #( BASE mt_components
                        ( NEW ztest_table_entity(
                            iv_compname = <comp>-compname
                            io_data_ref = lo_comp_descr
                            iv_is_root = abap_false
                          )
                        )
                      ).
                    CATCH cx_sy_move_cast_error.
                      TRY.
                          lr_symbref ?= <quick_data>.
                          DATA(d_ref) = lr_symbref->datref.
                          mt_components = VALUE #( BASE mt_components
                            ( NEW ztest_ref_entity(
                                iv_key = <comp>-compname
                                iv_type = |DATA::{ lr_symbref->instancename }|
                              )
                            )
                          ).
                        CATCH cx_sy_move_cast_error.
                      ENDTRY.
                      TRY.
                          lr_symbobjref ?= <quick_data>.
                          DATA(o_ref) = lr_symbobjref->objref.
                          mt_components = VALUE #( BASE mt_components
                            ( NEW ztest_ref_entity(
                                iv_key = <comp>-compname
                                iv_type = |OBJ::{ lr_symbobjref->instancename }|
                              )
                            )
                          ).
                        CATCH cx_sy_move_cast_error.
                      ENDTRY.
                  ENDTRY.
              ENDTRY.
          ENDTRY.
      ENDTRY.
    ENDLOOP.
*      CATCH cx_sy_move_cast_error.
*    ENDTRY.

Table-Level:

The class cl_tpda_script_tabledescr is offering a method get_line_handle( ) which returns one of the subclasses of CL_TPDA_SCRIPT_DATA_DESCR. This reference can as well be used to find the correct type via downcasting.

* Target-References for typecasts
    DATA: lr_simple  TYPE REF TO cl_tpda_script_elemdescr,
          lr_struct  TYPE REF TO cl_tpda_script_structdescr.
        ...
        lo_table_descr ?= io_data_ref.

        DO lo_table_descr->linecnt( ) TIMES.
          TRY.
              lo_line_desc = lo_table_descr->get_line_handle( p_line = lv_line_cnt ).
              lr_struct ?= lo_line_desc.
              mt_components = VALUE #( BASE mt_components
                ( NEW ztest_table_line_ent(
                    io_data_descr = lo_line_desc
                    iv_table_name = iv_compname
                    iv_tab_index = sy-index
                    iv_is_root = abap_false
                  )
                )
              ).
            CATCH cx_sy_move_cast_error.
              TRY.
                  lr_simple ?= lo_line_desc.
                  DATA(lv_simple) =  lr_simple->value( ) .
                  mt_components = VALUE #( BASE mt_components
                    ( NEW ztest_simple_table(
                        iv_value = lv_simple
                        iv_comp_name = iv_compname
                        iv_tab_idx = sy-index
                      )
                    )
                  ).
...

So the root object builds itself recursevliy by adding all its child elements.

Value-Statement

The value-statement ist generated by just calling get_content( ) on the root entity. This method will run through the object and call itself recursively on its child elements.

  METHOD get_content.
    APPEND |{ me->get_key( ) } = value #( | TO rt_content.
    LOOP AT mt_components ASSIGNING FIELD-SYMBOL(<comp>).
      APPEND LINES OF <comp>->get_content( ) TO rt_content.
    ENDLOOP.
    APPEND |)| TO rt_content.
  ENDMETHOD.

The Debugger-Script itself is pretty straight foreward:

*** insert your script code here
    me->break( ).

* Read VAriable Name via selection-screen
    CALL FUNCTION 'Z_CALL_SEL_SCREEN'
      IMPORTING
        ev_varname = lv_var_name
      EXCEPTIONS
        cancel     = 1                " Abbruch d. Benutzer
        OTHERS     = 2.
    BREAK-POINT.
    IF sy-subrc <> 0.
      MESSAGE 'Cancelled by User...' TYPE 'I'.
      RETURN.
    ENDIF.

* The Debugger-Script just creates the root entities
* -> The tree-like structure is mainly build in the corresponding constructors of
* the classes ztest_table_entity and ztest_struct_entity (which take as input the lo_type_descr)
    TRY.

        lo_type_desc = cl_tpda_script_data_descr=>factory( p_var_name = lv_var_name ).

        TRY.
* Cast to Structure-Type First
            lo_struct_descr ?= lo_type_desc.
            lo_struct = NEW #( iv_compname = lv_var_name
                               io_data_descr = lo_type_desc
                               iv_is_root = abap_true
                             ).

            CALL FUNCTION 'Z_DISPLAY_ENTITY_AS_TREE'
              EXPORTING
                ir_entity = lo_struct.                 " ztest_compl_entity

          CATCH cx_sy_move_cast_error.
            TRY.
* Cast to Table-Type
                lo_table_descr ?= lo_type_desc.
                lo_table = NEW #( iv_compname = lv_var_name
                                  io_data_ref = lo_type_desc
                                  iv_is_root = abap_true
                                ).

                CALL FUNCTION 'Z_DISPLAY_ENTITY_AS_TREE'
                  EXPORTING
                    ir_entity = lo_table.                 " ztest_compl_entity

              CATCH cx_sy_move_cast_error.
            ENDTRY.
        ENDTRY.
      CATCH cx_tpda_varname cx_tpda_data_descr_invalidated INTO DATA(lo_err).                " TPDA: Variable existiert nicht
        MESSAGE lo_err->get_text( ) TYPE 'I'.
    ENDTRY.

More Examples:


I thought it might be useful to see if reference elements are contained:

* Nested structure with and without ref
  ls_nested = VALUE #(
    without_ref = VALUE #( some_int = 3 some_string = |Teststring| )
    with_ref = VALUE #( some_int = 5 some_d_ref = REF #( 5 ) some_o_ref = NEW lcl_test( ) )
  ).

SAP ABAP Tutorial and Material, SAP ABAP Certification, SAP ABAP Exam Prep, SAP ABAP Guides
Structure with references

Since any derived class of ztest_entity must implement get_content( ) method, you can drill down in the alv_tree to the child nodes by double clicking on it.  The nested statement can be changed inline in the text-control and is propageted back to the root entity.

SAP ABAP Tutorial and Material, SAP ABAP Certification, SAP ABAP Exam Prep, SAP ABAP Guides
changing value inline

A “side-effect” of this tool is that a complex structure/table can be searched for values via CTRL-F without drilling down to its child element in the debugger (Sometimes I am wondering where some values come from).

Has anyone other strategies or tips to handle complex value statements? Do you use the SQL-Console-Value Tool in ADT?

No comments:

Post a Comment