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

Getting acquainted with automating ABAP unit testing – Part 3

Continue the process of writing the first unit test

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

Part 2 – Begin the process of writing the first unit test

To recap from the preceding blog, we changed our example program to include a virtually empty unit test class. Here is the source code as we left it in the previous blog:

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.
*======================================================================
*
*   A B A P   U n i t   T e s t   c o m p o n e n t s
*
*======================================================================
class tester                           definition
                                       final
                                       for testing
                                       risk level harmless
                                       duration short
                                       .
  private section.
    methods      : set_alv_field_catalog
                 .
endclass.
class tester                           implementation.
  method set_alv_field_catalog.
  endmethod.
endclass.

 

Running existing unit tests

Fire up the ABAP Editor and retrieve or create the program containing the code shown above, then activate it. Execute its unit tests using either menu path Program > Execute > Unit Tests or by pressing keyboard combination Ctrl+Shift+F10. You should find the following status message appears at the bottom of the screen:

Processed: 1 program, 0 test classes, 0 test methods

Notice that it states zero for test classes. We know this is not true because there certainly is a test class defined for this program, a local class named tester appearing at the end of this ABAP source code. Meanwhile, the message also states zero for test methods. This is true because we have not yet indicated that any methods of local class tester should be recognized by the ABAP Unit Testing Framework as methods to be executed during a unit test, so let’s change this right now.

Also, a comment posted by Andrea Borgia on Part 2 of this blog series states executing unit tests as described here that “on HANA 1909 I get the same ‘no tests’ message (SABP_UNIT102) after adding the local test class, both in Eclipse and in the Java GUI”.  Accordingly, I guess I should state for the record that the message appearing above is issued when executing on a 7.40 system.  Your messages may vary when using other versions.

Designating an automated unit test method for execution

Include the “for testing” clause on the “methods” statement appearing in the private section of the class definition for class tester, so that it looks like this:

private section.
  methods : set_alv_field_catalog
              for testing
          .

Afterward, activate the program.

Consequences of using the “for testing” clause

Adding the “for testing” clause to a methods statement of a unit test class is how it becomes marked as a method to be executed by the ABAP Unit Testing Framework. You’ll notice this is the second time we have used the “for testing” clause: This time it was added to the “methods” statement but this clause already appears on the “class … definition” statement.

The “for testing” clause, when applied to a “methods” statement, will designate the associated method as one to be invoked during the execution of a unit test. To use an oversimplified explanation of the process that actually takes place, it means that when you select the ABAP Editor menu path Program > Execute > Unit Tests or by pressing keyboard combination Ctrl+Shift+F10, it will cause the “test runner”, a component of the ABAP Unit Testing Framework, to begin executing. The test runner will read the activated version of the source code upon which it is to run the tests, parsing through the statements looking for local class definitions having the “for testing” clause as well as, for those, any methods statements also having the “for testing” clause. It then invokes those unit test methods one after the other.

As a consequence, this is how each method of the local test class gains control to perform whatever testing activities are defined for it. At this point our only unit test method, named set_alv_field_catalog, contains no ABAP statements, so it has no testing activities to be performed, but that will change soon enough. Despite our unit test method being devoid of any executable statements, the test runner still will invoke the method, and, because it contains no statements, the called method will simply allow control to return to its caller.

Another consequence of using the “for testing” clause is applicable to the one used on the class definition statement. By default, classes that are marked “for testing” will not be compiled in a production environment. This is how ABAP unit test code, residing locally as part of the object containing the production code, is prevented from ever gaining control in a production environment. It represents a significant safeguard against the accidental or intentional execution of automated unit tests in a production environment. Indeed, once marked as “for testing”, a local class may have none of its members referenced by any portion of the production code – the syntax checker will catch instances where this is attempted and flag it accordingly as an error. This also insures that only code eligible to be compiled in the production system is referenced by the production code.

Running the unit test again

Execute the unit tests again. As a result of the changes to the code, we should now find that the former status message of

Processed: 1 program, 0 test classes, 0 test methods

now shows the following:

Processed: 1 program, 1 test classes, 1 test methods

It indicates that the test runner executed and this time found a single test method in a single local test class, exactly our scenario at this point.

Executing the program again

Despite the changes to the code, we should continue to find no difference when we execute the program and provide Airline ‘AA’ – it still results in an error message indicating “No flights match carrier AA”. Try this now.

Summary

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

  • The ABAP Unit Test Framework has a component called the test runner, a component that gains control when we are in the ABAP Editor and select menu path Program > Execute > Unit Tests or by pressing keyboard combination Ctrl+Shift+F10.
  • The test runner of the ABAP Unit Test Framework will parse through the activated version of the associated ABAP source code searching for local classes marked “for testing” and any of its methods also marked “for testing”.
  • Applying the “for testing” clause to a methods statement of a local class indicates that we want the test runner to recognize this method as one to be executed during the automated unit test run.
  • Applying the “for testing” clause to a local class definition statement will prevent the entire class from being compiled in a production environment.
  • A local class containing the “for testing” clause on its class definition statement must have none of its members referenced by any portion of the production code; the source code would otherwise fail a syntax check.

What’s next?

We’ve defined an automated unit test class named tester which contains an automated unit test method named set_alv_field_catalog, but that method remains devoid of any code to specify the testing activities to be performed. Providing testing activities to be performed by a unit test method will be covered in the blog representing part 4 of this 10-part series Getting acquainted with automating ABAP unit testing.

Assigned Tags

      3 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Bärbel Winkler
      Bärbel Winkler

      James E. McDonough

      Hi James,

      thanks for publishing this series of blog posts and for the baby steps approach you decided to use! I'm looking forward to following along.

      Cheers

      Bärbel

      Author's profile photo Timothy Muchena
      Timothy Muchena

      James E. McDonough, you are a legend!

      Author's profile photo Andrea Borgia
      Andrea Borgia

      Andrea (missing n)

      Anyway, thanks, I have updated the comment linking to the relevant parts.