Skip to Content
Technical Articles
Author's profile photo Mykola Tokariev

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

Hello community,

this article describe solution to mock the data without need it to COPY + PASTE them.

In this approach you need 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. 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 operations (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 implements 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 = 'No serislized object was found.'.
    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). Is it not a great?! πŸ™‚

I hope this was understandable.

Assigned Tags

      7 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Paul Hardy
      Paul Hardy

      In higher releases SAP supplies class CL_OSQL_REPLACE which tricks actually SQL statements into reading from a Z table during unit tests. However I am not sure that is a good idea. I am pretty much against doing any sort of database access in unit tests. The "code pal" code inspector tests give you an error if it finds that sort of thing.

      One reason is that database access slows down the tests and you really need to be able to execute hundreds of tests in seconds.

      Also as soon as you have a Z table a test reads from, then it is possible (unlikely but possible) that the data in the Z table changes over time, leading to tests that once passed now not passing, or vice versa despite the test being unchanged.

      I do agree that cutting and pasting vast amounts of data is a pain. I always point people to

      https://github.com/sbcgua/mockup_loader

      In this case the data is read from an excel sheet stored inside the database. Of course you might say that is exactly the same as the method you describe....

       

      Author's profile photo Mykola Tokariev
      Mykola Tokariev
      Blog Post Author

      Thank you for the comment and explanation. I'm absolute agree with this points and I will not recommend to use this approach for unit-test mocking.

      Author's profile photo Oleg Bashkatov
      Oleg Bashkatov

      .

      Author's profile photo Oleg Bashkatov
      Oleg Bashkatov

      Mykola, thank you for sharing you vision for mock-data.

      I have noticed that you missed realization for ref-objects. Why?

      I mean class ZCL_MOCK, method set_data

            WHEN cl_abap_typedescr=>kind_ref.
      
            WHEN OTHERS.
              RETURN.

      Sometime ago I have using the similar approach for testing and it DOES SPEED UP testing (does not matter what is saying a man in a hat πŸ™‚ even if man in a hat named Paul Hardy πŸ™‚ I am not intend to advertise his book here... ).

      If it is ok - I could also share approach what I was using.

      But I prefer not to call it unit-testing (actually it is not unit), but model-testing, which I think we need most from ABAP and SAP ERP itself. But it is only my opinion.

      Author's profile photo Mykola Tokariev
      Mykola Tokariev
      Blog Post Author

      Hey Oleg,

      thank you for the comment.

      In this blog I just want to show an approach to serialize and de-serialize the data. It's not perfect and fully finished solution yet πŸ™‚ Also some places with possible exceptions are not covered. Interesting for me is to get an opinion from sap-community.

      Sure the place with

      WHEN cl_abap_typedescr=>kind_intf.
      WHEN cl_abap_typedescr=>kind_class. 
      WHEN cl_abap_typedescr=>kind_ref.

      should be implemented πŸ™‚

      And sure I would be glad to see your approach what you using!

      Β 

      Β 

      Author's profile photo Paul Hardy
      Paul Hardy

      I think we have a difference of semantics here.

      Having a large amount of test cases which can run in an automatic fashion coming from some sort of data source is certainly faster than manually typing in the same amount of data into unit test code.

      One example I know of is a company who does mapping software. They have tens of thousands of addresses they check after every change to make sure the software change as not "moved" 10 Downing Street to Africa or somewhere.

      It is the same with Postman - when we change something on the back or front end hundreds if not thousands of automated Postman calls are made to make sure nothing has been broken.

      But as you say those are not unit tests - they are more like ECATT. In other words you read and write to/from the database. Sometimes tests like that can take hours to run. That does not matter as you are only interested in the results and can do other things in the meantime.

      A unit test should have no database access because you want to run hundreds or thousands of them in a second, because you run them all the time whilst you are programming, maybe every time you change a line of code (you can set this up in ADT by the way with the continuous integration plug in). It is like the pretty printer or the syntax check. Both of those are instant and so can run after every single change - but if they took a long time you would not run them until maybe the end of the day, and so the time taken to detect errors would increase.

      Also thank you for not advertising my book. It would be really bad to mention I have a new edition of my book out, so I also am not going to mention that there is a new edition of my book out. I certainly would not use this opportunity to direct people to my log about it

      https://blogs.sap.com/2021/11/23/i-can-see-abap-moon-rising-part-two-of-eight/

       

      Author's profile photo Frederik Hudak
      Frederik Hudak

      Internally we have a similar solution which stores data in eCatt test data containers (object type ECTD).

      The containers have the nested structure

      • container
        • table of variants
          • table of parameters

      for access

      • there is an API which can be used to manipulate the objects and implement the basic CRUD operations you mention
      • RFC is supported
      • the data can be maintained manually in the SECATT transaction
      • various buttons can be implemented which allow someone to save a test case based on what they just executed
      • a BADI can be implemented to record whatever is happening on the system for the given filter values and save it as test cases

      We use this as

      • container (to group similar test cases)
        • tested object
          • input (tested object is called with input)
          • output (call result is compared against the output stored here)
          • metadata (whatever else, for example you can store the original runtime measurement and compare with a later run)
          • (extra parameters can be used to hold data of dependencies, but the minimal configuration is just input/output)

      So a container can have multiple tested objects, and each object has multiple input/output pairs. As long as you keep everything in json/xml this is a general-purpose solution.

      Note that you can work with typed data structures in ECTD objects but if you have deeper structures you will go insane if you try to edit them manually. So we use this as a poor man's document store where you can copy-paste some json/xml if needed.

      The data can be imported/exported manually, or with abapgit as this is a development object with a package assignment. The serialization format is not friendly as the whole container is saved as a singel xml file, but you do get the ability to revert to earlier versions.

      This just takes care of the data storage, so to execute the tests, there are two ways:

      • unit test class which will loop over everything in a container, execute some logic with the data and assert that expected = actual
      • same, but displays the results in an alv grid. this allows to show results in a friendlier way

      As a lot of this is independent of what you are testing, if you're willing to publish your interfaces in a github repository, I could contribute some example implementations...