This is a test message to test the length of the message box.
Login
ABAP Unit Function Double
Created by Software-Heroes

ABAP Unit - TDF (Function Double)

1054

In this article, let's take a look at how we can deal with function modules as dependencies without testing them as well.

It's been a while since the last article on unit testing. At that time we had actually already completed the series on the subject of automatic testing, but in the meantime SAP has made additional features available. There is now a test double for function modules, which we want to take a closer look at in this article.

 

Introduction

A test double is there to eliminate dependencies on called objects. This means that whenever a database is queried, a service called, another class started or a function module implemented, you get dependencies. Since you only want to check your own code in a unit test, such dependencies should disappear at test runtime. There is the concept of doubles for this, the dependency is exchanged and you get control over the return values. Tests are stable and repeatable again.

 

Preperation

For this we need a dependency, in this special case a function module. To do this, we create a test module that performs arithmetic operations. We only implement the operations for addition and subtraction:

FUNCTION z_bs_demo_test_function
  IMPORTING
    VALUE(id_input_one) TYPE i
    VALUE(id_input_two) TYPE i
    VALUE(id_method) TYPE string
  EXPORTING
    VALUE(ed_result) TYPE i.

  CASE id_method.
    WHEN '+'.
      ed_result = id_input_one + id_input_two.
    WHEN '-'.
      ed_result = id_input_one - id_input_two.
  ENDCASE.
ENDFUNCTION.

 

Configuration

First of all, we need a global level variable to hold our test environment. As for databases and CDS views, there is a separate environment:

CLASS-DATA:
  go_function TYPE REF TO if_function_test_environment.

 

First of all we initialize the environment and transfer the function modules for which we want to create a test double.

" Initialize double
go_function = cl_function_test_environment=>create( VALUE #( ( 'Z_BS_DEMO_TEST_FUNCTION' ) ) ).

 

Next we get the double that we want to configure, this is done via the name of the function module.

" Get double for function module
DATA(lo_demo_function) = go_function->get_double( 'Z_BS_DEMO_TEST_FUNCTION' ).

 

Then we create the input configuration, this set defines a state. If all parameters are passed as defined when calling the function module, then this set is triggered.

" Configure input configuration
DATA(lo_input_config) = lo_demo_function->create_input_configuration(
  )->set_importing_parameter( name  = 'ID_INPUT_ONE' value = 10
  )->set_importing_parameter( name  = 'ID_INPUT_TWO' value = 10
  )->set_importing_parameter( name  = 'ID_METHOD' value = '*'
).

 

Matching the input configuration, an output configuration must now also be defined. This provides the output parameters of the function module with values.

" Configure output configuration
DATA(lo_output_config) = lo_demo_function->create_output_configuration(
  )->set_exporting_parameter( name = 'ED_RESULT' value = 100
).

 

In the last step, the two configurations must now be brought together. For this we configure the call, if the input values correspond to the default, then the output should be set according to the default.

" Set matching configuration
lo_demo_function->configure_call( )->when( lo_input_config )->then_set_output( lo_output_config ).

 

Possibilities

In addition to being able to set the output directly, you can also simulate other behavior, such as the output of classic exceptions, OO exceptions or a direct response. The double behaves like the test double for classes. You can get more information in the interface:

 

Test case

Let's now define a first abstract test case to test this behavior. To do this, we call the function module directly in our test case. Normally we would test the global class and actually want to deactivate the function module. To illustrate, let's just test the function module once to test the actual double:

DATA:
  ld_result TYPE i.

CALL FUNCTION 'Z_BS_DEMO_TEST_FUNCTION'
  EXPORTING
    id_input_one = 10
    id_input_two = 10
    id_method    = '*'
  IMPORTING
    ed_result    = ld_result.

cl_abap_unit_assert=>assert_equals( act = ld_result exp = 100 ).

 

We create the other test cases (see complete example), whereby one test case should not work. Here we do not create any parameters for a combination. This should not find any configuration. This means that scenarios that were actually not defined in the function module at all also work:

 

Full example

As always, the entire example of the global test class with the appropriate comments and definitions:

"! @testing Z_BS_DEMO_TEST_FUNCTION
CLASS zcl_bs_demo_funcdouble DEFINITION PUBLIC FINAL CREATE PUBLIC
  FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PUBLIC SECTION.
  PROTECTED SECTION.
  PRIVATE SECTION.
    CLASS-DATA:
      go_function TYPE REF TO if_function_test_environment.

    CLASS-METHODS:
      class_setup,
      class_teardown.

    METHODS:
      not_defined_method FOR TESTING,
      wrong_substraction FOR TESTING,
      not_defined_case FOR TESTING.
ENDCLASS.


CLASS zcl_bs_demo_funcdouble IMPLEMENTATION.
  METHOD class_setup.
    go_function = cl_function_test_environment=>create( VALUE #( ( 'Z_BS_DEMO_TEST_FUNCTION' ) ) ).

    DATA(lo_demo_function) = go_function->get_double( 'Z_BS_DEMO_TEST_FUNCTION' ).

    " Configure test double - First call
    DATA(lo_input_config) = lo_demo_function->create_input_configuration(
      )->set_importing_parameter( name  = 'ID_INPUT_ONE' value = 10
      )->set_importing_parameter( name  = 'ID_INPUT_TWO' value = 10
      )->set_importing_parameter( name  = 'ID_METHOD' value = '*'
    ).

    DATA(lo_output_config) = lo_demo_function->create_output_configuration(
      )->set_exporting_parameter( name = 'ED_RESULT' value = 100
    ).

    lo_demo_function->configure_call( )->when( lo_input_config )->then_set_output( lo_output_config ).

    " Configure test double - Second call
    lo_input_config = lo_demo_function->create_input_configuration(
      )->set_importing_parameter( name  = 'ID_INPUT_ONE' value = 20
      )->set_importing_parameter( name  = 'ID_INPUT_TWO' value = 10
      )->set_importing_parameter( name  = 'ID_METHOD' value = '-'
    ).

    lo_output_config = lo_demo_function->create_output_configuration(
      )->set_exporting_parameter( name = 'ED_RESULT' value = 11
    ).

    lo_demo_function->configure_call( )->when( lo_input_config )->then_set_output( lo_output_config ).
  ENDMETHOD.


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


  METHOD not_defined_method.
    DATA:
      ld_result TYPE i.

    CALL FUNCTION 'Z_BS_DEMO_TEST_FUNCTION'
      EXPORTING
        id_input_one = 10
        id_input_two = 10
        id_method    = '*'
      IMPORTING
        ed_result    = ld_result.

    cl_abap_unit_assert=>assert_equals( act = ld_result exp = 100 ).
  ENDMETHOD.


  METHOD wrong_substraction.
    DATA:
      ld_result TYPE i.

    CALL FUNCTION 'Z_BS_DEMO_TEST_FUNCTION'
      EXPORTING
        id_input_one = 20
        id_input_two = 10
        id_method    = '-'
      IMPORTING
        ed_result    = ld_result.

    cl_abap_unit_assert=>assert_equals( act = ld_result exp = 11 ).
  ENDMETHOD.


  METHOD not_defined_case.
    DATA:
      ld_result TYPE i.

    CALL FUNCTION 'Z_BS_DEMO_TEST_FUNCTION'
      EXPORTING
        id_input_one = 15
        id_input_two = 20
        id_method    = '+'
      IMPORTING
        ed_result    = ld_result.

    cl_abap_unit_assert=>assert_equals( act = ld_result exp = 35 ).
  ENDMETHOD.
ENDCLASS.

 

Conclusion

Now it is also possible to easily test function modules in our objects and to do without test seams. Function modules will be used less and less in the future, but as long as they exist, there should also be a way to mock them.

 

Source:
Help Portal - ABAP Function Modules


Included topics:
ABAP UnitABAPUnit TestsTest-Double-FrameworkFunction module
Comments (0)



And further ...

Are you satisfied with the content of the article? We post new content in the ABAP area every Friday and irregularly in all other areas. Take a look at our tools and apps, we provide them free of charge.


ABAP Unit - Automation

Category - ABAP

When do objects break during development and what side effects can adjustments have? In this article we take a closer look at the topic.

01/05/2024

RAP - ABAP Unit (Service)

Category - ABAP

How do you actually test the service of an interface or RAP app automatically? In this article we look at unit tests for OData services.

01/20/2023

RAP - ABAP Unit (Business Object)

Category - ABAP

At this point, we'll take a look at how we can test our RAP Business Object automatically in order to check all important functions at the touch of a button.

01/13/2023

ABAP Unit - Tips

Category - ABAP

At the end of the series, a few tips that we would like to give you along the way. This is about shortcuts and general information about the tests.

11/12/2021

ABAP Unit - Software architecture

Category - ABAP

What could the target architecture look like in a SAP system if we look at our own software components? We want to clarify this in this article.

11/05/2021