Skip to Content
Technical Articles

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

Hello Community!

 

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:

Debugger%20Script%20Start

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.

 

Value-Statement%20Output

Value-Statement Output + Structure of Object

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

With%20flight%20connections

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( ) )
  ).

Structure%20with%20references

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.

changing%20value%20inline

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?

I can clean up the code and post it here if anyone is interested (or upload to git, which I haven’t done yet in ABAP :)).

Feel free to give some feedback on this approach!

This is my first blog by the way 🙂 Maybe you have some tips here as well!

Cheers,

Alex

 

 

2 Comments
You must be Logged on to comment or reply to a post.