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

Getting acquainted with automating ABAP unit testing – Part 2

Begin the process of writing the first unit test

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

Part 1 – Initiating the execution of automated ABAP unit tests

To recap from the preceding blog, we created a program having no object-oriented statements, then initiated the execution of its non-existent automated unit tests, either through ABAP Editor menu path Program > Execute > Unit Tests or by pressing keyboard combination Ctrl+Shift+F10, and received the warning message indicating there are no automated unit tests for it. 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.

Running existing unit tests

Fire up the ABAP Editor and retrieve or create the program containing the program code shown above, activate it and execute its unit tests. It should result in the following warning message presented in the ABAP editor:

The program <program name> contains no executable unit tests

Understanding the concepts associated with automating ABAP unit testing

The next step is to understand some of the concepts associated with automated unit testing with ABAP. When we initiated the execution of its non-existent automated unit tests, whether through ABAP Editor menu path Program > Execute > Unit Tests or by pressing keyboard combination Ctrl+Shift+F10, it triggered the execution of a component of the ABAP Unit Testing Framework, a facility provided by SAP to supplement the various ABAP program editors (SE38, SE37, SE24, SE80, etc.) with the capability of writing and executing automated unit tests.

This facility first became available with NetWeaver release 6.40 (2004). Accordingly, there are bazillions of customized programs written and placed into production long before this facility ever became available. In contrast, other languages, notably Java, to which I suspect ABAP exhibits Java-envy based on many of the enhancements to the language recently applied, have had automated unit testing frameworks available much longer. Many of the automated unit testing frameworks for these other languages are based on the concepts embodied in the xUnit testing model, and this is also the case with ABAP.

With an xUnit test, the test itself is written in the same language as the production code it tests. This is how the test can be automated – it is written in ABAP and so can be executed as an ABAP program. Another feature of xUnit tests is that the unit testing framework provides a way for the test to interact with it, essentially enabling the ABAP automated unit test code to convey to the testing framework what it is to do in performing a unit test. Yet another feature of xUnit tests is that only a simple completion message is produced when all tests have run successfully, but when at least one test has failed then a report is presented with analysis of what had failed.

The ABAP language was enhanced to accommodate the designation of code that is intended to be part of a unit test as opposed to part of the production code. In addition, the ABAP Unit Testing Framework, which can be triggered from within the ABAP Editor to execute unit tests, provides its own way for the unit test code to communicate what should be tested, as we shall see with the examples to be used throughout this blog series.

Requirements of writing an automated ABAP unit test

In order to work at all there are some requirements associated with how a unit test can be written in ABAP. One of these requirements is that the unit test code itself must be written as a local class using the object-oriented model of programming. For some readers, that alone is enough to induce anxiety, shortness of breath, muscle spasms, dizziness, dry mouth, cold sweat and a host of other physical and psychological ailments, as well as for them to discontinue reading this blog at this very sentence. For those who right now might be trembling with despair upon realizing that some object-oriented code might be required, let me put you at ease: Automated unit tests written for ABAP will require only the most minimal aspects of the object-oriented model, as you will soon see if you continue with this blog series.

Writing our first automated unit test for ABAP

Fire up the ABAP Editor and retrieve or create the program containing the code shown above, then copy the following code to the end of the existing code:

*======================================================================
*
*   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.

This is a single object-oriented class written as ABAP code. If you unfamiliar with object-oriented ABAP then there is no statement in this new code you will recognize, so for the benefit of those readers, this will be explained using familiar concepts.

The first thing to notice are the first two statements of the final 3 lines:

method set_alv_field_catalog.
endmethod.

This is the object-oriented equivalent to the form-endform subroutine definition found with procedural ABAP, as in:

form set_alv_field_catalog.
endform.

It also bears resemblance to the function-endfunction definition associated with function modules:

function set_alv_field_catalog.
endfunction.

Now that it is expressed in procedural terms, you’ll probably notice that this method contains no ABAP statements, and for now that is intentional.

The next thing to notice is the final statement and the statement appearing 3 lines above it defining a class-endclass construct:

class tester implementation.
  o
  o
endclass.

This can loosely be considered the counterpart to a function group defining a collection of function modules. The ABAP component containing the function group would be an object named SAPLxxx, where “xxx” is the name of the function group. Accordingly, for class “tester” shown above the counterpart function group object would be SAPLTESTER. Just as a function group contains function modules defined using the function-endfunction construct, a class contains methods defined using the method-endmethod construct.

The next thing to notice is that there are two class-endclass constructs: The first contains the definition portion of the class and the second contains the implementation portion. These are complementary constructs which together define a complete class, hence both have the same name; in this case, the class name is “tester”.

The next thing to notice is that the class definition statement contains the following 3 clauses:

for testing
risk level harmless
duration short

All three of these clauses are additions to the ABAP language class statement to denote aspects of a class defined specifically as a unit test class. The “for testing” clause identifies this as a unit test class to the ABAP Unit Testing Framework while the “risk level” and “duration” clauses, which only accompany a class marked as “for testing”, indicate other aspects about how the unit test will run.

Next, notice the class definition portion includes the following lines:

private section.
  methods : set_alv_field_catalog
          .

The “private section” denotes a visibility marking, and for unit tests it is rare that a section other than “private section” would be required. The “methods” statement indicates the name and the signature, if any, for its methods. Notice the name on this “methods” statement indicates set_alv_field_catalog, which is the same name appearing on the “method” statement in the class implementation.

Finally, notice that when code containing complementary class-endclass constructs are included as part of the program to be tested, then it is considered a local class – that is, it is defined locally to the program.

That is about all you need to know at this point about object-oriented ABAP in order to be able to define local classes containing automated unit tests.

Running the new unit test

Now activate the program. After that has been completed then once again invoke the automated unit tests for this program through menu path Program > Execute > Unit Tests or by pressing keyboard combination Ctrl+Shift+F10. Unlike what we saw with a program that had no automated unit test definitions, this time we see appearing at the bottom of the screen a status message indicating the following:

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

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 Testing Framework is based on the xUnit testing model, which means:
    • An automated unit test is written in the same language as the code to be tested (as we saw with our unit test defined above).
    • The automated unit testing framework provides a way for the test to interact with it and specify the characteristics of the unit test (we saw one aspect of this with the class definition statement including the 3 clauses “for testing”, “risk level” and “duration”).
    • When all tests are successful we see only a status message (as illustrated in the example above).
    • When any test fails we are presented with a report analyzing the failure (we have not yet seen this).
  • ABAP unit tests are written in ABAP as object-oriented classes defined locally within the same program as the code to be tested.
  • Even though none of the methods of a unit test class contain any executable code, the fact that a unit test class is defined for an ABAP program is enough to cause a status message to be issued instead of a warning message issued indicating there are no test classes.

What’s next?

Despite getting a status message indicating the number of programs, test classes and test methods taking part in the automated unit test, we have not yet specified any test to be performed upon the production code. The process to begin this will be covered in the blog representing part 3 of this 10-part series Getting acquainted with automating ABAP unit testing.

Assigned Tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Andrea Borgia
      Andrea Borgia

      First off, nice to see you back here, your abap2xlsx / email sample code from a much older post was very useful!

      Anyhow, I suppose tabname should really be "SFLIGHT" both here and in part 1, right?

      EDIT: it is intentional, see part4

      Also, 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.

      EDIT: use 7.40, see part3

      (it's really an HANA issue, apparently: both with Eclipse and the Windows GUI on AS ABAP 7.52SP04 it gives the expected message, whereas HANA gives "no tests" as written above)

       

      Author's profile photo Sergio Racca
      Sergio Racca
      Andrea, I had the same problem with HANA 1909 in both GUI and Eclipse.
      Just adding for testing clause in the definition of the set_alv_field_catalog method, it would seem that everything works ok.