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

ABAP Unit - Testable Code (Part 3)

1030

Here we take a closer look at the options for deactivating dependent components in your own coding during the test period.



This article is about the methodology of dependency injection to deactivate components used during the test and thus to make your own code testable.

 

Term

This is about the technology of how to deal with dependent components and give the tester or caller the option of deactivating or overwriting such components.

Here is a “bad” example in which the caller hardly has a chance to deactivate the dependent component. The object is instantiated in the class and used immediately afterwards:

CLASS zcl_test_handle_docs DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      constructor,

      call_selector
        IMPORTING
                  id_flag          TYPE abap_bool
        RETURNING VALUE(rd_result) TYPE string.
ENDCLASS.


CLASS zcl_test_handle_docs IMPLEMENTATION.
  METHOD constructor.
  ENDMETHOD.

  METHOD call_selector.
    NEW zcl_test_doc( )->do_stuff_and_select_data( id_flag ).
  ENDMETHOD.
ENDCLASS.

 

Using this example, you will now learn different methods of how you can replace the component with your own component. Here we will use a test double.

 

Preparation

In order for the strongly coupled class to be decoupled in the method, the class has to be rebuilt a bit. The instance variable should be swapped out as a member variable (attribute of the class) and the instantiation should be separated from the use accordingly. After the conversion, the class would look like this:

CLASS zcl_test_handle_docs DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      constructor,

      call_selector
        IMPORTING
                  id_flag          TYPE abap_bool
        RETURNING VALUE(rd_result) TYPE string.

  PRIVATE SECTION.
    DATA:
      mo_doc TYPE REF TO zif_test_doc.  
ENDCLASS.


CLASS zcl_test_handle_docs IMPLEMENTATION.
  METHOD constructor.
    mo_doc = NEW zcl_test_doc( ).
  ENDMETHOD.

  METHOD call_selector.
    mo_doc->do_stuff_and_select_data( id_flag ).
  ENDMETHOD.
ENDCLASS.

 

We also create a dummy implementation in the local area, which exchanges the DOC and makes it available according to our logic. The name “LTD_” (Local Test Double) already shows you what the purpose of the class is.

CLASS ltd_doc DEFINITION FINAL.
  PUBLIC SECTION.
    INTERFACES: zif_test_doc.
ENDCLASS.


CLASS ltd_doc IMPLEMENTATION.
  METHOD zif_test_doc~do_stuff_and_select_data.
    " Empty implementation
  ENDMETHOD.
ENDCLASS.

 

Constructor Injection

We offer an optional parameter via the constructor that overwrites the DOC. If we do not pass our own object, a corresponding instance of the concrete class is created.

CLASS zcl_test_handle_docs DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      constructor
        IMPORTING
          io_doc TYPE REF TO zif_test_doc OPTIONAL,      

      call_selector
        IMPORTING
                  id_flag          TYPE abap_bool
        RETURNING VALUE(rd_result) TYPE string.

  PROTECTED SECTION.
  PRIVATE SECTION.
    DATA:
      mo_doc TYPE REF TO zif_test_doc.  
ENDCLASS.


CLASS zcl_test_handle_docs IMPLEMENTATION.
  METHOD constructor.
    mo_doc = io_doc.

    IF mo_doc IS NOT BOUND.
      mo_doc = NEW zcl_test_doc( ).
    ENDIF.  
  ENDMETHOD.


  METHOD call_selector.
    mo_doc->do_stuff_and_select_data( id_flag ).
  ENDMETHOD.
ENDCLASS.

 

The test class with the application of the method now looks like this:

CLASS ltc_constructor_injection DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    METHODS:
      get_data FOR TESTING.
ENDCLASS.


CLASS ltc_constructor_injection IMPLEMENTATION.
  METHOD get_data.
    DATA(lo_local_doc) = NEW ltd_doc( ).

    DATA(lo_cut) = NEW zcl_test_handle_docs( lo_local_doc ).

    DATA(ld_result) = lo_cut->call_selector( abap_false ).

    cl_abap_unit_assert=>assert_initial( ld_result ).
  ENDMETHOD.
ENDCLASS.

 

When generating the CUT, we can now transfer our own test double and thus control the behavior of our codes during the test.

 

Setter Injection

The class provides its own setter method which can be used to replace the DOC. This method can then be called before using the CUT.

CLASS zcl_test_handle_docs DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      constructor,      

      call_selector
        IMPORTING
                  id_flag          TYPE abap_bool
        RETURNING VALUE(rd_result) TYPE string,

      set_component
        IMPORTING
          io_doc TYPE REF TO zif_test_doc.
          
  PROTECTED SECTION.
  PRIVATE SECTION.
    DATA:
      mo_doc TYPE REF TO zif_test_doc.  
ENDCLASS.


CLASS zcl_test_handle_docs IMPLEMENTATION.
  METHOD constructor.
    mo_doc = NEW zcl_test_doc( ).
  ENDMETHOD.


  METHOD call_selector.
    mo_doc->do_stuff_and_select_data( id_flag ).
  ENDMETHOD.
  
  
  METHOD set_component.
    mo_doc = io_doc.
  ENDMETHOD.  
ENDCLASS.

 

The test class with the application of the method now looks like this:

CLASS ltc_setter_injection DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    METHODS:
      get_data FOR TESTING.
ENDCLASS.


CLASS ltc_setter_injection IMPLEMENTATION.
  METHOD get_data.
    DATA(lo_local_doc) = NEW ltd_doc( ).

    DATA(lo_cut) = NEW zcl_test_handle_docs( ).
    lo_cut->set_component( lo_local_doc ).
    
    DATA(ld_result) = lo_cut->call_selector( abap_false ).

    cl_abap_unit_assert=>assert_initial( ld_result ).
  ENDMETHOD.
ENDCLASS.

 

Parameter Injection

The parameter injection works with an optional transfer parameter when the method is called. If an instance is transferred, then this is used, otherwise a new instance of the used class is simply created.

CLASS zcl_test_handle_docs DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      constructor,      

      call_selector
        IMPORTING
                  id_flag          TYPE abap_bool
                  io_doc           TYPE REF TO zif_test_doc OPTIONAL
        RETURNING VALUE(rd_result) TYPE string.
          
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.


CLASS zcl_test_handle_docs IMPLEMENTATION.
  METHOD constructor.
  ENDMETHOD.


  METHOD call_selector.
    DATA(lo_doc) = CAST zif_test_doc( COND #(
      WHEN io_doc IS BOUND
      THEN io_doc
      ELSE NEW zcl_test_doc( )
    ) ).

    lo_doc->do_stuff_and_select_data( id_flag ).  
  ENDMETHOD.  
ENDCLASS.

 

The test class with the application of the method now looks like this:

CLASS ltc_parameter_injection DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    METHODS:
      get_data FOR TESTING.
ENDCLASS.


CLASS ltc_parameter_injection IMPLEMENTATION.
  METHOD get_data.
    DATA(lo_local_doc) = NEW ltd_doc( ).

    DATA(lo_cut) = NEW zcl_test_handle_docs( ).

    DATA(ld_result) = lo_cut->call_selector( 
        id_flag = abap_false 
        io_doc = lo_local_doc ).

    cl_abap_unit_assert=>assert_initial( ld_result ).
  ENDMETHOD.
ENDCLASS.

 

Backdoor Injection

With backdoor injection, nothing in the global class has to be adjusted. The backdoor is generated via the FRIEND addition. The test class with the application of the method now looks like this:

CLASS ltc_backdoor_injection DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    METHODS:
      get_data FOR TESTING.
ENDCLASS.

CLASS zcl_test_handle_docs DEFINITION LOCAL FRIENDS ltc_backdoor_injection.

CLASS ltc_backdoor_injection IMPLEMENTATION.
  METHOD get_data.
    DATA(lo_local_doc) = NEW ltd_doc( ).

    DATA(lo_cut) = NEW zcl_test_handle_docs( ).
    lo_cut->mo_doc = lo_local_doc.

    DATA(ld_result) = lo_cut->call_selector( abap_false ).

    cl_abap_unit_assert=>assert_initial( ld_result ).
  ENDMETHOD.
ENDCLASS.

 

Usage

With the four injection mechanisms, you have a number of tools at your disposal that you can use during development. In the most common cases, the constructor and the backdoor injection are used because they promise the least amount of effort and are flexible for the test. It is only important to separate the creation from the use of the class.

If you have your own ideas for the injection mechanism, you can of course also use this. However, these well-known patterns are the most common.

 

Conclusion

There are various ways to deactivate dependent components, but there is always an option to make your code testable. When developing, always keep in mind that another developer may want to test or use your components as well.


Included topics:
ABAP UnitABAPUnit TestsTestable CodeDependency Injection
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 - Test execution

Category - ABAP

What options are there for executing ABAP Unit and what hidden functions might you not know about? More details about the different modes in this article.

06/25/2024

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

ABAP Unit - TDF (Function Double)

Category - ABAP

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

04/07/2023

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