Skip to Content
Technical Articles
Author's profile photo James E. McDonough

Getting acquainted with automating ABAP unit testing – Part 1

Initiating the execution of automated ABAP unit tests

This blog represents part 1 of this 10-part series Getting acquainted with automating ABAP unit testing.

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).

Summary

So, let’s summarize what we have learned here:

  • The ABAP Editor provides the capability to execute automated unit tests.
  • ABAP unit tests can be written for programs that contain no object-oriented code at all.
  • Initiating the execution of automated unit tests from within the ABAP Editor is performed through menu path Program > Execute > Unit Tests or by pressing keyboard combination Ctrl+Shift+F10.
  • The attempt to run automated unit tests for a program having none will produce a warning message alerting us to their absence.

What’s next?

Since attempting to initiate the execution of automated unit tests against a program having none results in a warning message, the next step is to write an automated unit test for this program, a program which at this point contains no object-oriented code. This will be covered in the blog representing part 2 of this 10-part series Getting acquainted with automating ABAP unit testing.

 

Assigned Tags

      5 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Matthew Billingham
      Matthew Billingham

      I hoping the final result of this blog will be a program that's fully in a test harness and doesn't use any obsolete code like "FORMS".

      I watch with interest!

      Author's profile photo Paul Hardy
      Paul Hardy

      You are not going to be disappointed.

      Jim has to start with 100% procedural code and no tests as (sadly) that is what people are used to. All the current examples you see on the internet start with OO code, and that puts people off.

      So you have to move people from A to B one little tiny baby step at a time, each time stressing why this baby step in particular makes you better off than you were before. Los of little clearly defined small changes that make you better off a tiny bit at a time work much better than "Start using OO because it is good" or "TDD is really good, look at all these OO Java examples!"

      It's like that quote Eddie Murphy uses in "Coming to America" - "you must walk to fly - you cannot fly to fly"

      BTW as a disclaimer myself and Jim are an ABAP double act as he does a technical review of my books and I do a technical review of his. We are also planning an actual ABAP comedy double act.

      This is how we see ourselves:

      Two%20Ronnies

      Two Ronnies

       

      Author's profile photo Hugo de Groot
      Hugo de Groot

      James, looking forward to following this blog!

      Author's profile photo Tapodipta Khan
      Tapodipta Khan

      I agree even in today ABAP Unit Test or Unit Testing a whole is looked as an optional for many projects.

      There are various reasons to it,

      Development systems data is poor.

      Lack of understanding on how to perform a unit test.

      This blog should definitely help developers to understand the importance of automated unit tests.

      Thanks for taking the time to share.

       

      Author's profile photo Robert Forster
      Robert Forster

      Hi,

      sorry a lot of text for (me) trivia.

      What should be the difference between a unit test and a automated unit test?

      Excited to see the automation...