Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
al_01
Explorer

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 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 Output + Structure of Object


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


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 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 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