EDUCAÇÃO E TECNOLOGIA

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

Hello community,

at the last time I see a lot of topics about unit-test mock data approach.
All of them need to manually mocked the data. This is mos 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.

Done.

In the next steps we will build something like this:

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

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?! 🙂

Hopefully you get the point.