Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
pawansrirama
Explorer
917

Hi All,

In this Blog I would like Exhibit a Scenario Where I will be working with AUNIT test for function module with test double framework.

Introduction 

A Test double removes dependencies on external objects like databases, services, classes, or function modules during unit testing. This ensures that tests focus on your code and remain stable and repeatable by controlling return values. 

 Requirement 

We need a Global variable to maintain our test environment for function module. Let us create a global variable GV_FUNC_ENVR with reference IF_FUNCTION_TEST_ENVIRONMENT. 

 CLASS-DATA: GV_FUNC_ENVR TYPE REF TO IF_FUNCTION_TEST_ENVIRONMENT. 

pawansrirama_1-1721300953817.png

We configure the environment and assign the function modules that require test doubles. 

GV_FUNC_ENVR = CL_FUNCTION_TEST_ENVIRONMENT => CREATE (VALUE #(( ' ZPAW_FUNC_MODULE'))). 

pawansrirama_0-1721300859432.png

Where ZPAW_FUNC_AUNIT_TEST is the function module and CL_FUNCTION_TEST_ENVIRONMENT is the standard class. 

We acquire the double we want to configure by specifying the function module's name. 

DATA(LO_TESTDOUBLE_FUNCTION) = GV_FUNC_ENVR  

->GET_DOUBLE(‘ZPAW_FUNC_MODULE’). 

pawansrirama_2-1721301124984.png

Next, we establish the input configuration, which defines a specific state. When all parameters match the defined criteria during the function module call, this configuration is activated. Correspondingly, an output configuration must be specified to assign values to the function module's output parameters. 

 Configure Input parameter 

DATA(LO_INPUT_VALUE_CONFIG) = LO_TESTDOUBLE_FUNCTION->CREATE_INPUT_CONFIGURATION ( )->SET_IMPORTING_PARAMETER (NAME = ‘lv_carrier_id’ VALUE = ‘AA’). 

Configure Output parameter 

ADATA(LO_OUTPUT_VALUE_CONFIG) = LO_TESTDOUBLE_FUNCTION->CREATE_OUTPUT_CONFIGURATION ( )->SET_EXPORTING_PARAMETER (NAME = ‘lv_msg’ VALUE = ’Flight data found’). 

 pawansrirama_3-1721301250431.png

Now, we merge the two configurations. We configure the call so that when the input values match the defaults, the output is also set accordingly. 

Configure matching criteria 

LO_TESTDOUBLE_FUNCTION->CONFIGURE_CALL ( )-> WHEN(LO_INPUT_VALUE_CONFIG)->THEN_SET_OUTPUT (LO_OUTPUT_VALUE_CONFIG). 

pawansrirama_4-1721301447730.png

Furthermore, beyond directly setting outputs, you can simulate alternative behaviors such as triggering classic exceptions, object-oriented exceptions, or providing immediate responses.  

This functionality mimics the behavior found in class test doubles. Additional information can be found within the interface IF_FTD_OUTPUT_CONFIG_SETTER. 

pawansrirama_0-1721300336261.png

Function module under test.

 

FUNCTION zpaw_func_module
  IMPORTING
    VALUE(lv_carrier_id) TYPE /dmo/carrier_name
  EXPORTING
    VALUE(lv_msg) TYPE string
    VALUE(lv_num_flights) TYPE i
    VALUE(lv_total_seats) TYPE i.

  DATA: lt_flights TYPE TABLE OF /dmo/flight.
  " Check if input carrier ID is empty
  IF lv_carrier_id IS INITIAL.
    lv_msg = 'Error: Carrier ID cannot be empty'.
  ELSE.

    " Fetch data from /dmo/flight based on carrier ID
    SELECT * FROM /dmo/flight
      WHERE carrier_id = _carrier_id
      INTO TABLE _flights.

    " Determine message based on the result set
    lv_num_flights = lines( lt_flights ).

    IF lv_num_flights > 0.
      " Calculate total seats for the flights found
      LOOP AT lt_flights INTO DATA(ls_flight).
        lv_total_seats = lv_total_seats + ls_flight-seats_max.
      ENDLOOP.

      " Determine message based on total seats
      IF lv_total_seats > 500.
        lv_msg = 'High Demand: Total seats exceed 500 for Carrier ID '.
      ELSE.
        lv_msg = 'Success: flights found for Carrier ID '.
      ENDIF.
    ELSE.
      lv_msg = 'Info: No flights found for Carrier ID '.
    ENDIF.
  ENDIF.

ENDFUNCTION.

 

Complete code is mentioned below for AUNIT Test Class.

 

CLASS zcl_test_paw_func_aunit_test DEFINITION FOR TESTING 
DURATION SHORT 
RISK LEVEL HARMLESS. 

PRIVATE SECTION. 
METHODS: test_func_module_success FOR TESTING, 
         test_func_module_no_data FOR TESTING, 
         test_func_module_high_demand FOR TESTING, 
         test_func_module_empty_carrid FOR TESTING, 
         test_func_module_flight_init FOR TESTING, 
         test_func_module_low_demand FOR TESTING. 

CLASS-DATA: gv_func_envr TYPE REF TO if_function_test_environment. 

CLASS-METHODS: class_setup, 
               class_teardown. 
ENDCLASS. 

CLASS zcl_test_paw_func_aunit_test IMPLEMENTATION. 

METHOD class_setup. 
gv_func_envr = cl_function_test_environment=>create( VALUE #( ( 'ZPAW_FUNC_MODULE' ) ) ). 

 
*Mocking values for get details. 
DATA(lo_testdouble_function) = gv_func_envr->get_double( 'ZPAW_FUNC_MODULE' ). 

" Add 1st test double values 
DATA(lo_input_value_config) = lo_testdouble_function->create_input_configuration( 
)->set_importing_parameter( name = 'lv_carrier_id' value = 'AA' ). 
DATA(lo_output_value_config) = lo_testdouble_function->create_output_configuration( 
)->set_exporting_parameter( name = 'lv_msg' value = 'Flight data found' ). 
lo_testdouble_function->configure_call( )->when( lo_input_value_config )->then_set_output( lo_output_value_config ). 


" Add 2nd test double values 
lo_input_value_config = lo_testdouble_function->create_input_configuration( 
)->set_importing_parameter( name = 'lv_carrier_id' value = 'ZZ' ). 
lo_output_value_config = lo_testdouble_function->create_output_configuration( 
)->set_exporting_parameter( name = 'lv_msg' value = 'Flight data not found' 
). 
lo_testdouble_function->configure_call( )->when( lo_input_value_config )->then_set_output( lo_output_value_config ). 

" Add 3rd test double values 
lo_input_value_config = lo_testdouble_function->create_input_configuration( 
)->set_importing_parameter( name = 'lv_carrier_id' value = ' ' ). 
lo_output_value_config = lo_testdouble_function->create_output_configuration( 
)->set_exporting_parameter( name = 'lv_msg' value = 'Error: Carrier ID cannot be empty' ). 
lo_testdouble_function->configure_call( )->when( lo_input_value_config )->then_set_output( lo_output_value_config ). 

" Add 4th test double values 
lo_input_value_config = lo_testdouble_function->create_input_configuration( 
)->set_importing_parameter( name = 'lv_carrier_id' value = 'WW' ). 
lo_output_value_config = lo_testdouble_function->create_output_configuration( 
)->set_exporting_parameter( name = 'lv_msg' value = 'Internal table is empty' 
). 
lo_testdouble_function->configure_call( )->when( lo_input_value_config )->then_set_output( lo_output_value_config ). 

" Add 5th test double values 
lo_input_value_config = lo_testdouble_function->create_input_configuration( 
)->set_importing_parameter( name = 'lv_carrier_id' value = 'LF' ). 
lo_output_value_config = lo_testdouble_function->create_output_configuration( 
)->set_exporting_parameter( name = 'lv_msg' value = 'High Demand: Total seats exceed 500 for Carrier ID' ). 
lo_output_value_config = lo_testdouble_function->create_output_configuration( 
)->set_exporting_parameter( name = 'lv_total_seats' value = '800' ). 
lo_testdouble_function->configure_call( )->when( lo_input_value_config )->then_set_output( lo_output_value_config ). 

" Add 6th test double values 
lo_input_value_config = lo_testdouble_function->create_input_configuration( 
)->set_importing_parameter( name = 'lv_carrier_id' value = 'RH' ). 
lo_output_value_config = lo_testdouble_function->create_output_configuration( 
)->set_exporting_parameter( name = 'lv_msg' value = 'Low Demand: Total seats less than 500' ). 
lo_output_value_config = lo_testdouble_function->create_output_configuration( 
)->set_exporting_parameter( name = 'lv_total_seats' value = '100' 
). 
lo_testdouble_function->configure_call( )->when( lo_input_value_config )->then_set_output( lo_output_value_config ). 
ENDMETHOD. 

METHOD class_teardown. 
gv_func_envr->clear_doubles( ). 
ENDMETHOD. 

METHOD test_func_module_flight_init. 
DATA: lv_carrier_id TYPE /dmo/carrier_name VALUE 'WW', 
      lv_msg TYPE string,
      lv_num_flights TYPE i. 
CALL FUNCTION 'ZPAW_FUNC_MODULE' 
EXPORTING 
lv_carrier_id = lv_carrier_id 
IMPORTING 
lv_msg = lv_msg 
lv_num_flights = lv_num_flights. 
cl_abap_unit_assert=>assert_initial( 
act = lv_num_flights ). 
ENDMETHOD. 

METHOD test_func_module_no_data. 
DATA: lv_carrier_id TYPE /dmo/carrier_name VALUE 'ZZ',
      lv_msg TYPE string. 
CALL FUNCTION 'ZPAW_FUNC_MODULE' 
EXPORTING 
lv_carrier_id = lv_carrier_id 
IMPORTING 
lv_msg = lv_msg. 
cl_abap_unit_assert=>assert_equals( 
act = lv_msg 
exp = 'Flight data not found' ). 
ENDMETHOD. 

METHOD test_func_module_low_demand. 
DATA: lv_carrier_id TYPE /dmo/carrier_name VALUE 'RH', 
      lv_msg TYPE string, 
      lv_total_seats TYPE i. 
CALL FUNCTION 'ZPAW_FUNC_MODULE' 
EXPORTING 
lv_carrier_id = lv_carrier_id 
IMPORTING 
lv_msg = lv_msg 
lv_total_seats = lv_total_seats. 
cl_abap_unit_assert=>assert_equals( 
act = lv_total_seats 
exp = '100' 
msg = lv_msg ). 
ENDMETHOD. 

METHOD test_func_module_empty_carrid. 
DATA: lv_carrier_id TYPE /dmo/carrier_name VALUE ' ', 
      lv_msg TYPE string. 
CALL FUNCTION 'ZPAW_FUNC_MODULE' 
EXPORTING 
lv_carrier_id = lv_carrier_id 
IMPORTING 
lv_msg = lv_msg. 
cl_abap_unit_assert=>assert_not_initial( 
act = lv_msg 
msg = 'Error: Carrier ID cannot be empty' ). 
ENDMETHOD. 

METHOD test_func_module_success. 
DATA: lv_carrier_id TYPE /dmo/carrier_name VALUE 'AA',
      lv_msg TYPE string. 
CALL FUNCTION 'ZPAW_FUNC_MODULE' 
EXPORTING 
lv_carrier_id = lv_carrier_id 
IMPORTING 
lv_msg = lv_msg. 
cl_abap_unit_assert=>assert_equals( 
act = lv_msg 
exp = 'Flight data found' ). 
ENDMETHOD. 

METHOD test_func_module_high_demand. 
DATA: lv_carrier_id TYPE /dmo/carrier_name VALUE 'LF', 
      lv_msg TYPE string, 
      lv_total_seats TYPE i. 
CALL FUNCTION 'ZPAW_FUNC_MODULE' 
EXPORTING 
lv_carrier_id = lv_carrier_id 
IMPORTING 
lv_msg = lv_msg 
lv_total_seats = lv_total_seats. 
cl_abap_unit_assert=>assert_equals( 
act = lv_total_seats 
exp = '800' 
msg = lv_msg ). 
ENDMETHOD. 
ENDCLASS.  

 

Now Run as ABAP Aunit test with Coverage.  

pawansrirama_0-1721303861330.png

Now Run as ABAP Aunit test with Trace. 

pawansrirama_1-1721303964844.png

pawansrirama_2-1721303990222.png

 

 

 

 

5 Comments
Anoop_L
Explorer
0 Kudos

Exquisite Content 

Sandra_Rossi
Active Contributor
0 Kudos

It looks like basically the same topic as already posted here in 2022: Test Doubles are coming to Function Modules - SAP Community. What are the differences with that one?

pawansrirama
Explorer
0 Kudos

@Sandra_Rossi the blog which is specified by you does not have proper implementation of AUNIT test double for function module. The scenario which I have shown have different configuration or possibility which is not mentioned in that blog. This blog will be a good guideline. 

Sandra_Rossi
Active Contributor
matt
Active Contributor

I find the test double framework is overly complex and error prone. You forget to mention one FM, or one table and it will run the real one.

I prefer to create a separate class (dao) for all accesses outside of my program, implemented against an interface. My model class calls the dao methods for FM, database accesses or whatever, but never does any of these things itself.

In my unit tests, I create a local test double class implementing the interface, and inject that into the code under test. In this way I have complete control and it's considerably easier for anyone else to understand.

 

Labels in this area