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.
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.
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( ) )
).
No comments:
Post a Comment