Skip to Content
Technical Articles
Author's profile photo Emanuel Klenner

ABAP Unit Tests: Generate a VALUE # Statement for the Contents of an internal Table

Creating test data for a meaningful ABAP unit test can be challenging. The data preview in the ABAP Development Tools (ADT) for Eclipse can help with that for database tables or views.

Let’s take table T100 (ABAP messages) as an example. First open the table definition and then click on the F8 button to trigger the data preview.

ABAP%20Data%20Explorer%20in%20Eclipse

ABAP Data Explorer in Eclipse

When you right click within the Data Preview area, you get the context menu option:
‘Copy all rows as ABAP value statement’. If you don’t see this option, your ABAP Development Tools in Eclipse version might be outdated.

Choosing this options copies an ABAP VALUE # statement to the clipboard. This generated statement is used to populate an internal table with the structure of table T100.

Now you can add this statement into your unit test class, or here simply into a notepad application:

VALUE%20%23%20statement%20in%20notepad

VALUE # statement in notepad

This is great for database tables but often times you might also need the content of an internal table as an input for a method or function call.

To achieve this, we will create a little debugger script that can capture the content of an arbitrary internal table as a VALUE # statement.

First, create the following structure in the ABAP dictionary:

SE11%20view%20of%20structure%20ZDEBUGGER_SCRIPTING_S

SE11 view of structure ZDEBUGGER_SCRIPTING_S

Or simply copy and paste the following structure definition in the ADT structure editor:

@EndUserText.label : 'Fields for Use in Debugger Scripts'
@AbapCatalog.enhancement.category : #EXTENSIBLE_CHARACTER_NUMERIC
define structure zdebugger_scripting_s {
  itab             : ortabname;
  itab_from        : fsline;
  itab_to          : feline;
  all_table_fields : xfeld;

}
Next, go to transaction SAS to create the debugger script.
Transaction%20SAS%20with%20default%20script%20template%20loaded

Replace the coding in the editor with the following code:

*---------------------------------------------------------------------*
*       CLASS lcl_debugger_script DEFINITION
*---------------------------------------------------------------------*
*
*---------------------------------------------------------------------*
CLASS lcl_debugger_script DEFINITION INHERITING FROM  cl_tpda_script_class_super  .

  PUBLIC SECTION.
    DATA gv_tabname    TYPE tabname.
    DATA gv_tab_from   TYPE i.
    DATA gv_tab_to     TYPE i.
    DATA gt_content    TYPE tpda_scr_table_content_it.
    DATA gt_components TYPE tpda_scr_table_comp_it.
    DATA gv_all_fields TYPE xfeld.
    DATA gv_field_cnt  TYPE i.

    METHODS prologue  REDEFINITION.
    METHODS init      REDEFINITION.
    METHODS script    REDEFINITION.
    METHODS end       REDEFINITION.

    METHODS get_table_fields.
    METHODS build_val_pound_expression.

ENDCLASS.                    "lcl_debugger_script DEFINITION
*---------------------------------------------------------------------*
*       CLASS lcl_debugger_script IMPLEMENTATION
*---------------------------------------------------------------------*
*
*---------------------------------------------------------------------*
CLASS lcl_debugger_script IMPLEMENTATION.
  METHOD prologue.
*** generate abap_source (source handler for ABAP)
    super->prologue( ).
  ENDMETHOD.                    "prolog

  METHOD init.
    DATA lt_sval   TYPE STANDARD TABLE OF sval.
    DATA lv_answer TYPE c LENGTH 1.
    DATA lv_title  TYPE string.
    DATA ls_popup  TYPE ZDEBUGGER_SCRIPTING_S. "Just for the where used list

    IMPORT tabname TO gv_tabname FROM MEMORY ID sy-repid.

    lv_title = 'Script Control Parameters'(000).

    lt_sval = VALUE #( tabname = 'ZDEBUGGER_SCRIPTING_S'
                       ( fieldname = 'ITAB'             fieldtext = 'Internal Table Name'(001)  value = gv_tabname field_obl = abap_true )
                       ( fieldname = 'ALL_TABLE_FIELDS' fieldtext = 'Use all table fields'(002) value = abap_true                        )
                       ( fieldname = 'ITAB_FROM'        fieldtext = 'From record'(003)          value = '1'                              )
                       ( fieldname = 'ITAB_TO'          fieldtext = 'To record'(004)            value = '9999'                           )
                     ).

    CALL FUNCTION 'POPUP_GET_VALUES'
      EXPORTING
        popup_title     = lv_title
      IMPORTING
        returncode      = lv_answer
      TABLES
        fields          = lt_sval
      EXCEPTIONS
        error_in_fields = 1
        OTHERS          = 2.

    IF sy-subrc IS NOT INITIAL.
      raise_error( ).
    ENDIF.

    IF lv_answer = 'A'.
*     User canceled
      raise_error( ).
    ENDIF.

    LOOP AT lt_sval INTO DATA(ls_sval).
      CASE sy-tabix.
        WHEN 1.
          gv_tabname   = ls_sval-value.
          EXPORT tabname FROM gv_tabname TO MEMORY ID sy-repid.

        WHEN 2.
          gv_all_fields = ls_sval-value.

        WHEN 3.
          gv_tab_from  = ls_sval-value.

        WHEN 4.
          gv_tab_to    = ls_sval-value.

      ENDCASE.

    ENDLOOP.

  ENDMETHOD.                    "init
  METHOD script.
    DATA lo_tab     TYPE REF TO cl_tpda_script_tabledescr.
    DATA lt_val     TYPE tpda_scr_table_content_it.
    DATA lt_comp    TYPE tpda_scr_table_comp_it.
    DATA lv_records TYPE i.

    TRY.
        lo_tab ?= cl_tpda_script_data_descr=>factory( CONV #( gv_tabname ) ).

        lv_records = lo_tab->linecnt( ).

        IF lv_records < gv_tab_to.
          gv_tab_to = lv_records.
        ENDIF.

        lo_tab->content( EXPORTING p_line_from   = gv_tab_from
                                   p_line_to     = gv_tab_to
                         IMPORTING p_it_comp_val = gt_content ).

        gt_components = lo_tab->components( ).

        get_table_fields( ).

        build_val_pound_expression( ).

      CATCH cx_root INTO DATA(lo_root).
        RETURN.
    ENDTRY.

  ENDMETHOD.                    "script

  METHOD get_table_fields.
    DATA lt_sval   TYPE STANDARD TABLE OF spopli.
    DATA lv_answer TYPE c LENGTH 1.
    DATA lv_title  TYPE string.

    gv_field_cnt = lines( gt_components ).

    IF gv_all_fields = abap_true.
      RETURN.
    ENDIF.

    lv_title = 'Select Table Fields'(003).

    LOOP AT gt_components INTO DATA(ls_comp).
      APPEND VALUE #( selflag = abap_true varoption = ls_comp-compname ) TO lt_sval.
    ENDLOOP.

    CALL FUNCTION 'POPUP_TO_DECIDE_LIST'
      EXPORTING
        mark_flag          = abap_true
        mark_max           = 100
        textline1          = 'Fieldname'(005)
        titel              = 'Select Table Fields'(006)
      IMPORTING
        answer             = lv_answer
      TABLES
        t_spopli           = lt_sval
      EXCEPTIONS
        not_enough_answers = 1
        too_much_answers   = 2
        too_much_marks     = 3
        OTHERS             = 4.

    IF sy-subrc IS NOT INITIAL.
      RETURN.
    ENDIF.

    IF lv_answer = 'A'.
*     User canceled
      RETURN.
    ENDIF.

    CLEAR gv_field_cnt.

    LOOP AT lt_sval INTO DATA(ls_sval).
      IF ls_sval-selflag IS INITIAL.
        READ TABLE gt_components INDEX sy-tabix ASSIGNING FIELD-SYMBOL(<ls_comp>).
        CHECK sy-subrc IS INITIAL.

        CLEAR <ls_comp>-compname.
      ELSE.
        ADD 1 TO gv_field_cnt.
      ENDIF.

    ENDLOOP.

  ENDMETHOD.

  METHOD build_val_pound_expression.
    DATA lv_max_fields_per_line TYPE n LENGTH 2 VALUE '10'.
    DATA lv_max_field_length    TYPE i.

    DATA BEGIN OF ls_value.
    DATA record TYPE c LENGTH 255.
    DATA END OF ls_value.

    DATA lt_value LIKE STANDARD TABLE OF ls_value.

    LOOP AT gt_components INTO DATA(ls_comp).
      DATA(lv_length) = strlen( ls_comp-compname ).

      IF lv_length > lv_max_field_length.
        lv_max_field_length = lv_length.
      ENDIF.

    ENDLOOP.

    APPEND VALUE #( record = |{ gv_tabname CASE = LOWER } = VALUE #( | ) TO lt_value.

    LOOP AT gt_content ASSIGNING FIELD-SYMBOL(<ls_tabrecord>).
      IF gv_field_cnt > lv_max_fields_per_line.
        APPEND VALUE #( record  = '(' ) TO lt_value.
      ELSE.
        ls_value-record = '( '.
      ENDIF.

      LOOP AT <ls_tabrecord>-fields INTO DATA(lv_field).
        READ TABLE gt_components INDEX sy-tabix INTO ls_comp.
        CHECK sy-subrc IS INITIAL AND ls_comp-compname IS NOT INITIAL.

        CASE ls_comp-typid.
          WHEN 'D' OR 'T'.
            CHECK lv_field CN '0 '.

          WHEN 'I' OR 'N' OR 'P'.
            CHECK lv_field CN '0., '.

          WHEN 'C'.
            CHECK lv_field <> space.

        ENDCASE.

        IF gv_field_cnt > lv_max_fields_per_line.
          IF ls_comp-compname <> 'MANDT'.
            ls_value-record = | { ls_comp-compname WIDTH = lv_max_field_length } = '{ lv_field }' |.
          ELSE.
            ls_value-record = | { ls_comp-compname WIDTH = lv_max_field_length } = sy-mandt |.
          ENDIF.

          APPEND ls_value TO lt_value.
        ELSE.
          IF ls_comp-compname <> 'MANDT'.
            ls_value-record &&= | { ls_comp-compname } = '{ lv_field }' |.
          ELSE.
            ls_value-record &&= | { ls_comp-compname } = sy-mandt |.
          ENDIF.
        ENDIF.

      ENDLOOP.
      IF gv_field_cnt <= lv_max_fields_per_line
        AND ls_value IS NOT INITIAL.
        ls_value-record &&= ' )'.
        APPEND ls_value TO lt_value.
      ELSE.
        APPEND VALUE #( record = ')' ) TO lt_value.
      ENDIF.

      CLEAR ls_value.
    ENDLOOP.

    APPEND VALUE #( record = |).| ) TO lt_value.

    EDITOR-CALL FOR lt_value.

  ENDMETHOD.

  METHOD end.
*** insert your code which shall be executed at the end of the scripting (before trace is saved)
*** here

  ENDMETHOD.                    "end
ENDCLASS.                    "lcl_debugger_script IMPLEMENTATION​

Save the debugger script under a new name:
Save%20Debugger%20Script

Save Debugger Script

Make sure that the ‘Trigger’ for the script is set to ‘Execute Directly’.

Let’s test the script with a little sample program:

REPORT z_test_value_pound_script.

START-OF-SELECTION.
  DATA lt_t100 TYPE TABLE OF t100.

  SELECT FROM t100
  FIELDS *
  WHERE sprsl = @sy-langu
  ORDER BY arbgb, msgnr
  INTO TABLE @lt_t100
  UP TO 1000 ROWS.

  BREAK-POINT.

Execute the report.

Test%20report%20with%20data%20loaded%20from%20table%20T100

Test report with data loaded from table T100

Switch to the ‘Script’ tab and load the newly created script:

Load%20Script%20ZRSTPDA_SCRIPT_VALUE_POUND

Load Script ZRSTPDA_SCRIPT_VALUE_POUND

Start the script:

Start%20Script

Start Script

The script will send a pop up window where you can specify the internal table name, here LT_T100, whether you want to use all of the fields from the table (Use all table fields = ‘X’ ) and which rows from the internal table you want to use.

Editor%20display%20of%20generated%20VALUE%20%23%20statement

Editor display of generated VALUE # statement

Now you can copy and paste the generated statement into your unit test class.

If you don’t need all the fields from the table, then deselect the ‘Use all table fields’ indicator.

Do%20not%20want%20all%20the%20table%20fields%20and%20only%205%20rows

Do not want all the table fields and only 5 rows

In this case you get an extra pop up to select, which fields from the table you actually want:

Field%20selection

Field Selection

And here the result:

Restricted%20result%20set

Restricted Result Set

The script should work fine for internal tables with a flat structure. I haven’t tested it with deep, nested structures.

In SAP S/4HANA on premise release 2022 this debugger script will be delivered under the name RSTPDA_SCRIPT_VALUE_POUND.

Example of test data in a unit test class generated with this script:

Generated%20data%20used%20in%20a%20test%20class%20method

Generated data used in a test class method

Have fun generating test data for your unit tests with this little tool.
I hope you find it helpful. Let me know what you think.

Also check the question and answer section for ABAP 
and the ABAP community topics.

Assigned Tags

      13 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Sandra Rossi
      Sandra Rossi

      Thanks. I like your solution because it's a standard solution based on ABAP Debugger Scripting. For information, there was also this tool which is based on Enhancement Framework (might not work if the debugger evolves but a little bit more comfortable to use than ABAP Debugger Scripting). This tool also works on structured variables, deep structures, internal tables with deep structures, long strings.

      Author's profile photo Emanuel Klenner
      Emanuel Klenner
      Blog Post Author

      Thanks for pointing out the solution via debugger enhancements Sandra. I also like that approach!

      Author's profile photo Mehmet Hangisi
      Mehmet Hangisi

      Hi Emanuel,

       

      Thanks for the post, didn't know something like this existed. This will save me some time when writing my tests.

      Author's profile photo Emanuel Klenner
      Emanuel Klenner
      Blog Post Author

      I hope it will be useful for you!

      Author's profile photo AjeethKumar R
      AjeethKumar R

      Thanks for the debugger Script. Gonna try. Its gonna ease off my work.

      Author's profile photo Emanuel Klenner
      Emanuel Klenner
      Blog Post Author

      Glad to hear.

      Author's profile photo Stalllone Christian
      Stalllone Christian

      Hello, Wow something new. We already created a utility to generate Aunit test data template for client , something similar and more powerful. but ABAP script is a good one, and a great example. let me explore more.

      Author's profile photo Emanuel Klenner
      Emanuel Klenner
      Blog Post Author

      Nice to hear. Thanks.

      Author's profile photo Thomas Fiedler
      Thomas Fiedler

      This feature will be available as a standard function in the ADT Debugger soon.

       

      Regards,

      Thomas.

       

      Author's profile photo Emanuel Klenner
      Emanuel Klenner
      Blog Post Author

      Excellent!

      Author's profile photo Jaroslav Hrbacek
      Jaroslav Hrbacek

      Great news. Looking forward!!!

      Author's profile photo Alexander Laible
      Alexander Laible

      I also wrote a script with the same purpose 🙂 There seems to be quite some interest in this topic. I just uploaded it to github (https://github.com/alaible/dbg_value_statement). This might also work on earlier releases. It was developed on SAP_BASIS 752.

       

      Author's profile photo Emanuel Klenner
      Emanuel Klenner
      Blog Post Author

      Cool 👍