Skip to Content
Author's profile photo Ankit Maskara

CDS Doubles – Writing Unit Tests for ABAP CDS

Hello Community,

This blog stems from my recent learning where I had to create CDS doubles to implement automation testing for the delivered ABAP CDS views.

Before going deeper in the topic I would like to thank below colleagues who helped me in realizing the same. Thank you Sunil Bandameedapalli  and Krishan Raheja.

The motivation and theory has been very well explained here, so I will start right away with my demo implementation and its variation. If you are new to this topic please read the above blog once to know the terminology.

I will demonstrate below two cases.

  1. CDS Under Test (CUT) with only tables as its Depended Upon Components(DOC).
  2. CUT with tables and other CDS views as its DOC.

Further variations and mix scenarios are also possible.

Before starting the implementation steps, I will outline the approach which I followed.

i) CUT identification – It should be non-trivial and have code pushdown or some complex join conditions.

ii) DOCs identification – In this step we breakdown the CUT into its components tables and other CDS views.

iii) Create a global abstract abap unit test class.

iv) Implement the local class within the step iii)’s global class which will do the following

a) Create fixture methods

b) Populate data in the DOCs

c) Execute the CDS view to fetch the data

d) Using Asserts compare the results

e) Test the CDS double

Please note: Above steps are same for both kinds of CDS test double implementation. Actually, case 2 is a minor variation of case 1.

In Detail –

Case 1.CDS Under Test (CUT) with only tables as its Depended Upon Components(DOC).

 

i) CUT identification –

This CDS derives data from multiple joins and hence becomes a candidate for automation testing.

@AbapCatalog.sqlViewName: 'IASORDDFCSA'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales order, Sales order document flow, customer sales data'
define view I_ASALESORDDOCFLWCUSTSALE
  as select from    vbap            as sales_item
    left outer join vbak            as sales_header      on sales_item.vbeln = sales_header.vbeln
    left outer join knvv            as customer_sales    on  customer_sales.kunnr = sales_header.kunnr
                                                         and customer_sales.vkorg = sales_header.vkorg
                                                         and customer_sales.vtweg = sales_header.vtweg
                                                         and customer_sales.spart = sales_header.spart
    left outer join vbfa            as sales_doc_flow    on  sales_item.vbeln       = sales_doc_flow.vbelv
                                                         and sales_item.posnr       = sales_doc_flow.posnv
                                                         and sales_doc_flow.vbtyp_n = 'J'
    left outer join likp            as delivery          on sales_doc_flow.vbeln = delivery.vbeln
    left outer join ztasalesofc as sales_office      on sales_office.sales_ofc = sales_header.kvgr5
                                                         or sales_office.sales_ofc = customer_sales.kvgr5
    left outer join ztataxofc_t as _Text             on  sales_office.tax_ofc_num = _Text.tax_ofc_num
                                                         and _Text.spras              = $session.system_language
    left outer join tvv5t           as sales_office_text on  (
                                                         sales_office_text.kvgr5  = sales_header.kvgr5
                                                         or sales_office_text.kvgr5 = customer_sales.kvgr5
      )
                                                         and sales_office_text.spras = $session.system_language

{
  sales_header.vbeln                       as    SalesDocument,
  sales_item.posnr                         as    SalesDocumentItem,
  sales_header.auart                       as    SalesDocumentType,
  customer_sales.vkorg                     as    SalesOrganization,
  sales_header.kvgr5                       as    SalesordSalesOffice,
  customer_sales.kunnr                     as    Customer,
  customer_sales.vtweg                     as    DistributionChannel,
  customer_sales.spart                     as    Division,
  customer_sales.kvgr1                     as    CustomerType,
  customer_sales.kvgr5                     as    CustSalesOffice,
  sales_doc_flow.vbeln                     as    Delivery,
  sales_doc_flow.posnn                     as    DeliveryItem,
  delivery.podat                           as    ProofOfDelivery,
  sales_office.tax_ofc_num                 as    TaxOffice,
  _Text.tax_ofc_name                       as    TaxOfficeName,
  _Text.spras                              as    language_tax_office,
  sales_office_text.bezei                  as    SalesOfficeText,
  sales_office_text.spras                  as    language_sales_office
}

 

ii) DOCs identification –

As evident from the CDS definition, the involved tables are – VBAP, VBAK, KNVV, VBFA, LIKP, ZTASALESOFC, ZTATAXOFC_T and TVV5T.

iii) The  created global class via SE24 is –

iv) Press ( Ctrl+Shift+F11 ) for the above global class and create a local class as below

*"* use this source file for your ABAP unit test classes

************************************************************************
* This CDS Double is used to test the below CDS
* CDS shows Sales order, Sales order document flow, customer sales data
************************************************************************
CLASS i_asalesorddocflwcustsale
 DEFINITION FINAL FOR TESTING
                    DURATION SHORT
                    RISK LEVEL HARMLESS.

  PRIVATE SECTION.

    CLASS-DATA: environment TYPE REF TO if_cds_test_environment.

    CLASS-METHODS: class_setup,
      class_teardown.

    METHODS: setup,
      dis_so_cust_data FOR TESTING RAISING cx_static_check.

    DATA: vbap       TYPE STANDARD TABLE OF vbap WITH EMPTY KEY,
          vbak       TYPE STANDARD TABLE OF vbak WITH EMPTY KEY,
          knvv       TYPE STANDARD TABLE OF knvv WITH EMPTY KEY,
          vbfa       TYPE STANDARD TABLE OF vbfa WITH EMPTY KEY,
          likp       TYPE STANDARD TABLE OF likp WITH EMPTY KEY,
          tasalesofc TYPE STANDARD TABLE OF ztasalesofc WITH EMPTY KEY,
          tataxofc_t TYPE STANDARD TABLE OF ztataxofc_t WITH EMPTY KEY,
          tvv5t      TYPE STANDARD TABLE OF tvv5t WITH EMPTY KEY.
ENDCLASS.

CLASS i_asalesorddocflwcustsale IMPLEMENTATION.

  
ENDCLASS.

a) Implement below fixture methods

  METHOD class_setup.
    " Instantiate the CUT and the inject the dependent on components( DOC )
    " This will create the doubles as well for the DOCs
    environment = cl_cds_test_environment=>create( i_for_entity = 'I_ASALESORDDOCFLWCUSTSALE'
                            i_dependency_list = VALUE #(
                                                         ( name = 'VBAP'                       type = 'TABLE'    )
                                                         ( name = 'VBAK'                       type = 'TABLE'    )
                                                         ( name = 'KNVV'                       type = 'TABLE'    )
                                                         ( name = 'VBFA'                       type = 'TABLE'    )
                                                         ( name = 'LIKP'                       type = 'TABLE'    )
                                                         ( name = 'ZTASALESOFC'            type = 'TABLE'    )
                                                         ( name = 'ZTATAXOFC_T'            type = 'TABLE'    )
                                                         ( name = 'TVV5T'                      type = 'TABLE'    )
                                                       ) ).

  ENDMETHOD.

  METHOD class_teardown.
    " Iterate over the double list and remove all
    environment->destroy( ).
  ENDMETHOD.

  METHOD setup.
    "Clean all the data in the doubles (Depenedent on components)
    environment->clear_doubles( ).
  ENDMETHOD.

b), c) and d) I have combined all three. You can try implementing these separately as well.

 

 METHOD dis_so_cust_data.

    " Populate the data in the double stubs
    vbap = VALUE #( ( vbeln = '1000000258' posnr = '000010' ) ).

    environment->insert_test_data( i_data = vbap ).

    vbak = VALUE #( ( vbeln = '1000000258' kunnr = '0000000104' vkorg = '2010'  vtweg = '10' spart ='10' kvgr5 = 'A15' auart  = 'ZA01' ) ).

    environment->insert_test_data( i_data = vbak ).

    knvv = VALUE #( ( kunnr = '0000000104' vkorg = '2010'  vtweg = '10' spart ='10' kvgr5 = 'A15' kvgr1 = 'Z01' ) ).

    environment->insert_test_data( i_data = knvv ).

    vbfa = VALUE #( ( vbelv = '1000000258' posnv = '000010'  vbtyp_n = 'J' vbeln = '2000000283' posnn = '000010' ) ).

    environment->insert_test_data( i_data = vbfa ).

    likp = VALUE #( ( vbeln = '2000000283' podat ='00000000' ) ).

    environment->insert_test_data( i_data = likp ).

    tasalesofc = VALUE #( ( sales_ofc = 'A15' tax_ofc_num = '01145' ) ).

    environment->insert_test_data( i_data =  tasalesofc ).

    tataxofc_t = VALUE #( ( tax_ofc_num = '01145' spras = 'E' tax_ofc_name = 'Test TaxOf' ) ).

    environment->insert_test_data( i_data = tataxofc_t ).

    tvv5t = VALUE #( ( kvgr5 = 'A15' spras = 'E' bezei = 'TEST Purpose' ) ).

    environment->insert_test_data( i_data = tvv5t ).



    " Execute the CUT which in turn will fetch the data from the doubles and
    " then validate the output
    SELECT * FROM i_asalesorddocflwcustsale
             INTO TABLE @DATA(lt_so_cust_data).

    cl_abap_unit_assert=>assert_equals( act = lines( lt_so_cust_data )
                                        exp = 1 ).

  ENDMETHOD.

e) Testing the double

Open the global class via SE24 and choose Execute Unit tests with coverage option as below –

The execution result will look like below –

 

Complete code base for case 1.

 

*"* use this source file for your ABAP unit test classes

************************************************************************
* This CDS Double is used to test the below CDS
* CDS shows Sales order, Sales order document flow, customer sales data
************************************************************************
CLASS i_asalesorddocflwcustsale
 DEFINITION FINAL FOR TESTING
                    DURATION SHORT
                    RISK LEVEL HARMLESS.

  PRIVATE SECTION.

    CLASS-DATA: environment TYPE REF TO if_cds_test_environment.

    CLASS-METHODS: class_setup,
      class_teardown.

    METHODS: setup,
      dis_so_cust_data FOR TESTING RAISING cx_static_check.

    DATA: vbap       TYPE STANDARD TABLE OF vbap WITH EMPTY KEY,
          vbak       TYPE STANDARD TABLE OF vbak WITH EMPTY KEY,
          knvv       TYPE STANDARD TABLE OF knvv WITH EMPTY KEY,
          vbfa       TYPE STANDARD TABLE OF vbfa WITH EMPTY KEY,
          likp       TYPE STANDARD TABLE OF likp WITH EMPTY KEY,
          tasalesofc TYPE STANDARD TABLE OF ztasalesofc WITH EMPTY KEY,
          tataxofc_t TYPE STANDARD TABLE OF ztataxofc_t WITH EMPTY KEY,
          tvv5t      TYPE STANDARD TABLE OF tvv5t WITH EMPTY KEY.
ENDCLASS.

CLASS i_asalesorddocflwcustsale IMPLEMENTATION.

  METHOD class_setup.
    " Instantiate the CUT and the inject the dependent on components( DOC )
    " This will create the doubles as well for the DOCs
    environment = cl_cds_test_environment=>create( i_for_entity = 'I_ASALESORDDOCFLWCUSTSALE'
                            i_dependency_list = VALUE #(
                                                         ( name = 'VBAP'                       type = 'TABLE'    )
                                                         ( name = 'VBAK'                       type = 'TABLE'    )
                                                         ( name = 'KNVV'                       type = 'TABLE'    )
                                                         ( name = 'VBFA'                       type = 'TABLE'    )
                                                         ( name = 'LIKP'                       type = 'TABLE'    )
                                                         ( name = 'ZTASALESOFC'                type = 'TABLE'    )
                                                         ( name = 'ZTATAXOFC_T'                type = 'TABLE'    )
                                                         ( name = 'TVV5T'                      type = 'TABLE'    )
                                                       ) ).

  ENDMETHOD.

  METHOD class_teardown.
    " Iterate over the double list and remove all
    environment->destroy( ).
  ENDMETHOD.

  METHOD setup.
    "Clean all the data in the doubles (Depenedent on components)
    environment->clear_doubles( ).
  ENDMETHOD.
  METHOD dis_so_cust_data.

    " Populate the data in the double stubs
    vbap = VALUE #( ( vbeln = '1000000258' posnr = '000010' ) ).

    environment->insert_test_data( i_data = vbap ).

    vbak = VALUE #( ( vbeln = '1000000258' kunnr = '0000000104' vkorg = '2010'  vtweg = '10' spart ='10' kvgr5 = 'A15' auart  = 'ZA01' ) ).

    environment->insert_test_data( i_data = vbak ).

    knvv = VALUE #( ( kunnr = '0000000104' vkorg = '2010'  vtweg = '10' spart ='10' kvgr5 = 'A15' kvgr1 = 'Z01' ) ).

    environment->insert_test_data( i_data = knvv ).

    vbfa = VALUE #( ( vbelv = '1000000258' posnv = '000010'  vbtyp_n = 'J' vbeln = '2000000283' posnn = '000010' ) ).

    environment->insert_test_data( i_data = vbfa ).

    likp = VALUE #( ( vbeln = '2000000283' podat ='00000000' ) ).

    environment->insert_test_data( i_data = likp ).

    tasalesofc = VALUE #( ( sales_ofc = 'A15' tax_ofc_num = '01145' ) ).

    environment->insert_test_data( i_data =  tasalesofc ).

    tataxofc_t = VALUE #( ( tax_ofc_num = '01145' spras = 'E' tax_ofc_name = 'Test TaxOf' ) ).

    environment->insert_test_data( i_data = tataxofc_t ).

    tvv5t = VALUE #( ( kvgr5 = 'A15' spras = 'E' bezei = 'TEST Purpose' ) ).

    environment->insert_test_data( i_data = tvv5t ).



    " Execute the CUT which in turn will fetch the data from the doubles and
    " then validate the output
    SELECT * FROM i_asalesorddocflwcustsale
             INTO TABLE @DATA(lt_so_cust_data).

    cl_abap_unit_assert=>assert_equals( act = lines( lt_so_cust_data )
                                        exp = 1 ).

  ENDMETHOD.



ENDCLASS.

 

Case 2. CUT with tables and other CDS views as its DOC.

All steps are same as above except the below ones –

iv) The data declaration for CDS views should be of type of the corresponding SQL view.

iv) a). In the class_setup fixture method the DOC will be declared of type ‘CDS_VIEW’

iv) b). Data will be populated in the CDS view instead of the table.(Syntax wise it remains the same as above).

 

As always, feedbacks and mutual learnings are most welcome.

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.