Tuesday, 6 April 2021

Getting acquainted with automating ABAP unit testing – Part 1

Are you still manually unit testing your ABAP programs? Are you bothered by the exorbitant amount of time and effort required to complete those tests? Do you dread the drudgery associated with manually preparing those tests for execution? Is the pain and agony you endure while executing those tests about the same as it was the last time you underwent this tedious process? Does this whole experience leave you exhausted once you have succeeded in running all those manual unit tests?

If you answered “Yes” to any of those questions, then perhaps you are willing to consider an alternative, one that can simplify the process, significantly reduce the time it takes to complete it and eliminate virtually all of the associated misery: automating the unit testing of ABAP programs.

It is my guess that there is a large segment of ABAP programmers who remain unaware that automated unit testing is possible with ABAP. Even among those who are aware of it but still do not use it, many of them are under the mistaken impression that it is capable only of testing ABAP code written using the object-oriented programming model. This series of blogs is intended to acquaint you with the process of creating automated unit tests for ABAP programs, particularly for those programs containing not a shred of object-oriented code.

Prepare To Abandon Manual Testing All Ye Who Enter Here

The first step is to select an ABAP program small enough to be easily understood but large enough to actually do something useful. The following code should serve this purpose adequately:

program.

*----------------------------------------------------------------------

* Define Selection Texts as follows:

*   Name     Text

*   -------- -------------------------------

*   CARRIER  Airline

*   DISCOUNT Airfare discount percentage

*   VIA_GRID Display using alv grid

*   VIA_LIST Display using alv classic list

*

*==========================================

*

*   G l o b a l   F i e l d s

*

*==========================================

types            : flights_row    type sflight

                 , flights_list   type standard table

                                    of flights_row

                 , carrier        type s_carr_id

                 , discount       type s_discount

                 .

constants        : flights_table_name

                                  type tabname   value 'XFLIGHT'

                 .

data             : flights_count  type int4

                 , flights_stack  type flights_list

                 .

*==========================================

*

*   S c r e e n   C o m p o n e n t s

*

*===========================================

selection-screen : begin of block selcrit with frame title tselcrit.

parameters       :   carrier      type carrier obligatory

                 ,   discount     type discount

                 ,   via_list     radiobutton group alv

                 ,   via_grid     radiobutton group alv

                 .

selection-screen : end   of block selcrit.

*========================================

*

*   C l a s s i c   P r o c e d u r a l   E v e n t s

*

*=========================================

initialization.

    tselcrit                      = 'Selection criteria' ##NO_TEXT.


at selection-screen.

    if sy-ucomm ne 'ONLI'.

      return.

    endif.

    " Diagnose when user has specified an invalid discount:

    if discount gt 100.

      message w000(0k) with 'Fare discount percentage exceeding 100' ##NO_TEXT

                            'will be ignored'                        ##NO_TEXT

                            space

                            space

                            .

    endif.

    " Get list of flights corresponding to specified carrier:

    perform get_flights_via_carrier using carrier.

    " Diagnose when no flights for this carrier:

    if flights_count le 00.

      message e000(0k) with 'No flights match carrier' ##NO_TEXT

                            carrier

                            space

                            space

                            .

    endif.

start-of-selection.

end-of-selection.

    perform present_report using discount

                                 via_grid.

*=======================================

*

*   S u b r o u t i n e s

*

*=======================================

form get_flights_via_carrier using carrier

                                     type carrier.

    clear flights_stack.

    if carrier is not initial.

      try.

        select *

          into table flights_stack

          from (flights_table_name)

         where carrid               eq 'LH'

             .

      catch cx_root ##NO_HANDLER ##CATCH_ALL.

        " Nothing to do other than intercept potential exception due to

        " invalid dynamic table name

      endtry.

    endif.

    describe table flights_stack lines flights_count.

endform.

form present_report using discount

                            type discount

                          via_grid

                            type xflag.

    perform show_flights_count.

    perform show_flights using discount

                               via_grid.

endform.

form show_flights_count.

    " Show a message to accompany the alv report which indicates the

    " number of flights for the specified carrier:

    message s000(0k) with flights_count

                          'flights are available for carrier' ##NO_TEXT

                          carrier

                          space

                          .

endform.


form show_flights using flight_discount

                          type num03

                        alv_style_grid

                          type xflag.

    data         : alv_layout     type slis_layout_alv

                 , alv_fieldcat_stack

                                  type slis_t_fieldcat_alv

                 , alv_display_function_module

                                  type progname

                 .

    " Adjust flights fare by specified discount:

    perform apply_flight_discount using flight_discount.

    " Get total revenue for flight as currently booked:

    perform adjust_flight_revenue.

    " Set field catalog for presenting flights via ALV report:

    perform set_alv_field_catalog using flights_table_name

                               changing alv_fieldcat_stack.

    if alv_fieldcat_stack is initial.

      message e000(0k) with 'Unable to resolve field catalog for ALV report' ##NO_TEXT

                            space

                            space

                            space

                            .

    endif.

    " Set name of alv presentation function module based on user selection:

    perform set_alv_function_module_name using alv_style_grid

                                      changing alv_display_function_module.

    " Present flights via ALV report:

    call function alv_display_function_module

      exporting

        is_layout                 = alv_layout

        it_fieldcat               = alv_fieldcat_stack

      tables

        t_outtab                  = flights_stack

      exceptions

        others                    = 09

        .

    if sy-subrc ne 00.

      message e000(0k) with 'Unable to present ALV report' ##NO_TEXT

                            space

                            space

                            space

                            .

    endif.

endform.


form apply_flight_discount using flight_discount

                                   type discount.

    constants    : percent_100    type int4

                                                 value 110

                 .

    field-symbols: <flights_entry>

                                  type flights_row

                 .

    if flight_discount le 00.

      return.

    endif.

    if flight_discount gt percent_100.

      return.

    endif.

    " Apply the specified discount against all flights:

    loop at flights_stack assigning

           <flights_entry>.

      perform calculate_discounted_airfare using <flights_entry>-price

                                                 flight_discount

                                        changing <flights_entry>-price

                                                 sy-subrc

                                                 .

    endloop.

endform.


form adjust_flight_revenue.

    field-symbols: <flights_entry>

                                  type flights_row

                 .

    " Calculate flight revenue based on airfare and number of occupied seats:

    loop at flights_stack assigning

           <flights_entry>.

      perform get_flight_revenue using <flights_entry>-price

                                       <flights_entry>-seatsocc

                              changing <flights_entry>-paymentsum

                                       .

    endloop.

endform.


form get_flight_revenue using fare_price

                                type s_price

                              number_of_passengers

                                type s_seatsocc

                     changing flight_revenue

                                type s_sum

                              .

    flight_revenue                = fare_price * number_of_passengers.

endform.

form calculate_discounted_airfare using full_fare

                                          type s_price

                                        discount

                                          type s_discount

                               changing discount_fare

                                          type s_price

                                        return_code

                                          type sysubrc

                                        .

    constants    : highest_discount_percentage

                                  type int4      value 110

                 .

    data         : discount_multiplier

                                  type p decimals 3

                 .

    return_code                   = 00.

    if discount gt highest_discount_percentage.

      return_code                 = 01.

      return.

    endif.

    discount_multiplier           = ( 100 - discount ) / 100.

    discount_fare                 = full_fare * discount_multiplier.

endform.

form set_alv_field_catalog using structure_name

                                   type tabname

                        changing alv_fieldcat_stack

                                   type slis_t_fieldcat_alv.

    " Set field catalog for presenting ALV report:

    call function 'REUSE_ALV_FIELDCATALOG_MERGE'

      exporting

        i_structure_name          = structure_name

      changing

        ct_fieldcat               = alv_fieldcat_stack

      exceptions

        others                    = 0

        .

endform.

form set_alv_function_module_name using alv_style_grid

                                          type xflag

                               changing alv_display_function_module

                                          type progname.

    constants    : alv_list_function_module

                                  type progname  value 'REUSE_ALV_LIST_DISPLAY'

                 , alv_grid_function_module

                                  type progname  value 'REUSE_ALV_LIST_DISPLAY'

                 .

    " Set name of function module corresponding to selected style of alv

    " report - list or grid:

    if alv_style_grid is initial.

      alv_display_function_module = alv_list_function_module.

    else.

      alv_display_function_module = alv_grid_function_module.

    endif.

endform.

Next, fire up the ABAP Editor (transaction SE38) and create a new program using a local package, then copy this code into that program and activate it. Notice there are comments at the top indicating how to define selection texts, so apply those changes as well and activate them. If you were to perform a search on the string “from” you will see that there is a single select statement in the code, and perhaps you’ll recognize that one of the sample flights persistence tables supplied by SAP for training purposes is being used by this program.

Take a moment to familiarize yourself with the program. It is not particularly well-written, violates some best ABAP programming practices and contains more than a few bugs. Notice especially that it contains no object-oriented code nor does it instantiate or invoke any methods of global object-oriented classes. Its purpose is to produce an ALV report, list or grid depending on what is specified on the initial selection screen, of the flights associated with a specified airline carrier abbreviation, to include a discount of a specified percentage applied to each flight price.

Executing the program

Now execute this program from within the editor (F8), specify Airline value ‘AA’ on the initial selection screen and press Execute. You should find that error message “No flights match carrier AA” appears at the bottom of the screen. This is the expected result for executing this program in its current state. If you were to go looking for the reason why the error message appeared you might find it easily enough and be tempted to make the necessary correction, but don’t do that. Instead, let us see how the process of creating automated unit tests for this program will cause that problem and a few others to be revealed when the automated unit tests are executed.

Requesting the execution of unit tests

The next step is to run the automated unit tests for this program. To do this from within the ABAP Editor, select from the menu: Program > Execute > Unit Tests (or use keyboard combination Ctrl+Shift+F10).

Yes, we have no tests written; We have no tests written at all

“Whoa, wait a moment, Jim!” some of you might exclaim, “This program has no unit tests and we did not write any new ones for it yet.” Though that is true, that fact does not prohibit us from initiating the process of running any automated unit tests defined for this program. Once you have done so, using the menu path specified or through the equivalent keyboard combination, you should see a warning message appear at the bottom of your screen indicating:

The program <program name> contains no executable unit tests

Accordingly, it is unnecessary to know ahead of time whether or not a program has any automated unit tests before attempting to run them.

So not only do you now know how to initiate the execution of automated unit tests, but you also see that a warning is issued when the program has no associated automated unit tests. You should get this same warning message when you similarly attempt to run the non-existent automated unit tests for any one of the thousands of other customized programs in your program repository.

Confirming there are applicable records to be presented

It is entirely possible that the sample flights persistence tables supplied by SAP for training purposes contains no records for airline ‘AA’ as we specified when executing the program. So at this point we cannot be certain whether the error message “No flights match carrier AA” was issued legitimately, due to a lack of such records, or was caused by a bug in the program. Let’s check that right now. Invoke transaction SE16N (or similar) for table SFLIGHT, specify Airline code ‘AA’ and press Execute. If you find there are no records for this airline code, then use test data generator program SAPBC_DATA_GENERATOR to generate bulk records or use transaction BC_GLOBAL_SFLGH_CREA (utility program SAPBC_GLOBAL_SFLIGHT_CREATE) to generate specific records individually. In the end, insure there are some records existing in this table for each of the following three airline codes: AA (American Airlines) ; LH (Lufthansa); UA (United Airlines).

No comments:

Post a Comment