Wednesday, 24 November 2021

How to save any data type to DB and restore them.

All of them need to manually mocked the data. This is most annoying work at the unit-tests.

After three days of work I get some solution to mock the data without need it to COPY + PASTE them.

In this approach all what you need it is to get the data in the preferred way (it can be SQL query, FUNCTION CALL and so one…), and save it to DB.

For example:

It can be some record from the table MARA and you know that your method should return this record. For this case you write one SQL query and save result to the Z-table. After you did it, you can always use the data from Z-table and DON’T NEED to mock MARA-data manually.

How should it works?

Two things, that we need: dynamic programming and serialization.

First thing first. We need a DB-Table to store our data. For this example we crate a table with two fields – MOCK_NAME and SERIALIZED. MOCK_NAME will be store the name of the mock object and SERIALIZED our serialized data.

SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Tutorial and Material, SAP ABAP Career, SAP ABAP Certification

Done.

In the next steps we will build something like this:

SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Tutorial and Material, SAP ABAP Career, SAP ABAP Certification

First step – we define the interface for serializable objects.

INTERFACE zif_serializable_object
  PUBLIC .
  INTERFACES if_serializable_object.

  METHODS set_data
    IMPORTING i_data TYPE any.

  METHODS read_mock_data
    RETURNING VALUE(r_result) TYPE REF TO data.

  METHODS how_is_my_name
    RETURNING VALUE(r_result) TYPE zmock_name.
ENDINTERFACE.

Done.

To avoid the dependencies in the future we create also an interface for creator. The class that implement this interface can CRUD operation (Create, Read, Update, Delete)

INTERFACE zif_mock_creater
  PUBLIC .

  METHODS: create_mock_obj
    IMPORTING i_mock_name     TYPE zmock_name
    RETURNING VALUE(r_result) TYPE REF TO zif_serializable_object.

  METHODS: read_mock_obj
    IMPORTING i_mock_name     TYPE zmock_name
    RETURNING VALUE(r_result) TYPE REF TO zif_serializable_object.

  METHODS: update_mock_obj
    IMPORTING i_mock          TYPE REF TO zif_serializable_object
              i_data          TYPE any
    RETURNING VALUE(r_result) TYPE REF TO zif_serializable_object.

  METHODS: delete_mock_obj
    IMPORTING i_mock_name     TYPE zmock_name
    RETURNING VALUE(r_result) TYPE sy-subrc.

ENDINTERFACE.

Now its time to implement serializable class.

CLASS zcl_mock DEFINITION
  PUBLIC
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES zif_serializable_object.

    METHODS constructor
      IMPORTING
        i_mock_name TYPE zmock_name.

  PROTECTED SECTION.
  PRIVATE SECTION.
    DATA: mock_name TYPE zmock_name,
          mock_data TYPE REF TO data.
ENDCLASS.

CLASS zcl_mock IMPLEMENTATION.

  METHOD constructor.
    me->mock_name = i_mock_name.
  ENDMETHOD.

  METHOD zif_serializable_object~how_is_my_name.
    r_result = mock_name.
  ENDMETHOD.

  METHOD zif_serializable_object~set_data.
    DATA: type_name  TYPE string.

    DATA(type) = cl_abap_typedescr=>describe_by_data( i_data ).

    CASE type->kind.
      WHEN cl_abap_typedescr=>kind_intf.
      WHEN cl_abap_typedescr=>kind_class.
      WHEN cl_abap_typedescr=>kind_elem.
        DATA(type_descr) = cl_abap_elemdescr=>describe_by_data( i_data ).
        type_name        = type_descr->get_relative_name( ).
        CREATE DATA mock_data TYPE (type_name).

      WHEN cl_abap_typedescr=>kind_struct.
        DATA(struc_descr) = CAST cl_abap_structdescr( cl_abap_datadescr=>describe_by_data( i_data ) ).
        type_name         = struc_descr->get_relative_name( ).
        CREATE DATA mock_data TYPE (type_name).

      WHEN cl_abap_typedescr=>kind_table.
        DATA(table_descr)  = CAST cl_abap_tabledescr( cl_abap_tabledescr=>describe_by_data( i_data ) ).
        DATA(line_decr)    = table_descr->get_table_line_type( ).
        type_name         = line_decr->get_relative_name( ).
        TYPES ty_sflight_lines TYPE STANDARD TABLE OF sflight WITH EMPTY KEY.
        CREATE DATA mock_data TYPE ty_sflight_lines.
      WHEN cl_abap_typedescr=>kind_ref.

      WHEN OTHERS.
        RETURN.
    ENDCASE.

    FIELD-SYMBOLS <dref> TYPE any.
    ASSIGN me->mock_data->* TO <dref>.
    <dref> = i_data.
  ENDMETHOD.

  METHOD zif_serializable_object~read_mock_data.
    r_result = mock_data.
  ENDMETHOD.

ENDCLASS.

How you can see this method implement zif_serializable_object interface. The most interesting method in this class is set _data(). There I check for the type of the input parameter and then assign it to mock_data attribute.

Last class in this chain is creator class.

CLASS zcl_mock_creator DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES zif_mock_creater.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_mock_creator IMPLEMENTATION.

  METHOD zif_mock_creater~create_mock_obj.
    r_result = NEW zcl_mock( i_mock_name ).
  ENDMETHOD.

  METHOD zif_mock_creater~read_mock_obj.
    TRY.
        DATA(deserialized_obj) = NEW zcl_mock_serializer( )->deserialize( i_mock_name ).
      CATCH zcx_mock_err INTO DATA(exc).

    ENDTRY.

    r_result = deserialized_obj.
  ENDMETHOD.

  METHOD zif_mock_creater~update_mock_obj.
    i_mock->set_data( i_data ).

    DATA(serialized_mock) = NEW zcl_mock_serializer( )->serialize( i_mock ).

    DATA(ser_line) = VALUE zmock_serialized( mock_name = i_mock->how_is_my_name( )
                                            serialized = serialized_mock ).

    MODIFY zmock_serialized FROM ser_line.
    COMMIT WORK.
  ENDMETHOD.

  METHOD zif_mock_creater~delete_mock_obj.
    DELETE FROM zmock_serialized WHERE mock_name = i_mock_name.
    r_result = sy-subrc.
  ENDMETHOD.

ENDCLASS.

In this class we define the methods for Create, Read, Update and Delete functions.

ZCL_MOCK_SERIALIZER just a helper that serialize and deserialize the object. The date which use ZCL_MOCK_SERIALIZER has the XSTRING type. The same type we use in our DB-Table to save the object.

CLASS zcl_mock_serializer DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    METHODS serialize
      IMPORTING i_object        TYPE REF TO zif_serializable_object
      RETURNING VALUE(r_result) TYPE xstring.

    METHODS deserialize
      IMPORTING i_mock_name    TYPE zmock_name
      RETURNING VALUE(r_result) TYPE REF TO zif_serializable_object
      RAISING   zcx_mock_err.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_mock_serializer IMPLEMENTATION.

  METHOD serialize.
    CALL TRANSFORMATION id SOURCE obj = i_object RESULT XML DATA(xstring)
        OPTIONS data_refs = 'heap-or-create'.

    r_result = xstring.
  ENDMETHOD.

  METHOD deserialize.
    SELECT SINGLE serialized
        FROM zmock_serialized
        INTO @DATA(xstring)
        WHERE mock_name  = @i_mock_name.

    IF xstring IS INITIAL.
      RAISE EXCEPTION TYPE zcx_mock_err
        EXPORTING
          textid = zcx_mock_err=>dyn_mess
          param1 = 'Keinen Serislized objekt wurde gefunden'.
    ENDIF.

    TRY.
        DATA deserialized_object TYPE REF TO zif_serializable_object.

        CALL TRANSFORMATION id SOURCE XML xstring RESULT obj = deserialized_object.
      CATCH cx_xslt_runtime_error.
        ROLLBACK WORK.
        RETURN.
    ENDTRY.

    r_result = deserialized_object.
  ENDMETHOD.

ENDCLASS.

Now is interesting part. So looks a test program:

SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Tutorial and Material, SAP ABAP Career, SAP ABAP Certification

We can choose any type to select and save the mock data.

REPORT.
FIELD-SYMBOLS <dref> TYPE any.

SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE title.

SELECTION-SCREEN BEGIN OF LINE.
PARAMETERS: p_rb1 RADIOBUTTON GROUP grp1 DEFAULT 'X'.
SELECTION-SCREEN COMMENT (15) lbl1 FOR FIELD p_rb1.

PARAMETERS: p_rb2 RADIOBUTTON GROUP grp1.
SELECTION-SCREEN COMMENT (15) lbl2 FOR FIELD p_rb2.

PARAMETERS: p_rb3 RADIOBUTTON GROUP grp1.
SELECTION-SCREEN COMMENT (15) lbl3 FOR FIELD p_rb3.
SELECTION-SCREEN END OF LINE.

SELECTION-SCREEN END OF BLOCK b1.

INITIALIZATION.
  title = 'Datentyp auswählen'.
  lbl1  = 'Integer:'.
  lbl2  = 'Structure:'.
  lbl3  = 'Table:'.

START-OF-SELECTION.
  DATA(mock_builder) = NEW zcl_mock_creater( ).

* Any data type
  CASE 'X'.
    WHEN p_rb1.
      DATA  numb TYPE i VALUE 5.
    WHEN p_rb2.
      SELECT SINGLE * FROM MARA INTO @DATA(mara_record).
    WHEN p_rb3.
      SELECT * FROM sflight UP TO 10 ROWS INTO TABLE @DATA(sflight_tab).
  ENDCASE.

* 1 Create
  DATA(mock) = mock_builder->zif_mock_creater~create_mock_obj( 'MY_MOCK_OBJECT' ).

* 2 Update
  CASE 'X'.
    WHEN p_rb1.
      mock_builder->zif_mock_creater~update_mock_obj( i_mock = mock  i_data = numb ).
    WHEN p_rb2.
      mock_builder->zif_mock_creater~update_mock_obj( i_mock = mock  i_data = mara_record ).
    WHEN p_rb3.
      mock_builder->zif_mock_creater~update_mock_obj( i_mock = mock  i_data = sflight_tab ).
  ENDCASE.

* 3 Read de-serialized from DB
  DATA(mock_data) = mock_builder->zif_mock_creater~read_mock_obj( 'MY_MOCK_OBJECT' )->read_mock_data( ).
  ASSIGN mock_data->* TO <dref>.

* 4 Delete
  mock_builder->zif_mock_creater~delete_mock_obj( 'MY_MOCK_OBJECT' ).

If you start the program, you can see that is no matter which data we want to save into DB. After read data from DB the helper class serialize the data and it will be saved into DB. After that we can read the data and get the same format of the data that we save into Z-table (MARA-Record for example). This data type we can use in our unit test. Is it not a great?!

No comments:

Post a Comment