ABAP Unit - Test framework
This article is about testing private methods and simply disabling dependent on components in code.
With the test framework, SAP offers additional tools and options for setting up clean tests. These include test doubles, SEAMs and the testing of private methods; we will take a closer look at the last two techniques in this article.
In order to better understand the object to be tested, we have listed the source code again here. The class consists of a public method and a private method. The private method gets data from two tables and returns it. The public method determines the data and outputs it via an additional component. If the output worked, a corresponding return flag is set.
CLASS zcl_bs_demo_private_access DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. METHODS: display_tools RETURNING VALUE(rd_displayed) TYPE abap_bool. PROTECTED SECTION. PRIVATE SECTION. TYPES: BEGIN OF ts_tools, short_name TYPE zbs_dy_tools-short_name, stock_quantity TYPE zbs_dy_tools-stock_quantity, description TYPE zbs_dy_toolst-description, END OF ts_tools, tt_tools TYPE STANDARD TABLE OF ts_tools WITH EMPTY KEY. METHODS: get_tool_data RETURNING VALUE(rt_tools) TYPE tt_tools. ENDCLASS. CLASS zcl_bs_demo_private_access IMPLEMENTATION. METHOD display_tools. DATA(lt_tools) = get_tool_data( ). TRY. rd_displayed = NEW zcl_test_display( )->display_generic_data( lt_tools ). CATCH cx_sy_data_access_error. rd_displayed = abap_false. ENDTRY. ENDMETHOD. METHOD get_tool_data. TRY. DATA(ld_language) = cl_abap_context_info=>get_user_language_abap_format( ). CATCH cx_abap_context_info_error. RETURN. ENDTRY. SELECT tool~short_name, tool~stock_quantity, text~description FROM zbs_dy_tools AS tool LEFT OUTER JOIN zbs_dy_toolst AS text ON text~short_name = tool~short_name AND text~language = @ld_language INTO TABLE @rt_tools. ENDMETHOD. ENDCLASS.
In different situations it can be important to test the private methods and attributes of a class. Here you should be aware that the most stable interface are the public methods of the class and that the internal part of the class can quickly look different with a simple refactoring.
So why do you test the private part of classes?
- “Interface class” - Usually has a processing method that starts all processing and has a large number of private methods with corresponding logic
- Using Injection - The private part of the class must be accessed for backdoor injection, more on this in the following chapters
- Important Methods - There are important and critical methods within the class that should be tested
Now let's look at the example above and consider the data discovery method to be particularly important and want to test it in a separate test case. In order to be able to access the private attributes and methods, we have to “befriend” the test class with the global class. Global friends have been around for a long time in the OO context in ABAP, local friends are still relatively new and are mainly used for test classes.
" 1 - Friends on TOP CLASS ltc_private_method DEFINITION DEFERRED. CLASS zcl_bs_demo_private_access DEFINITION LOCAL FRIENDS ltc_private_method. CLASS ltc_private_method DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. METHODS: get_full_database FOR TESTING RAISING cx_static_check. ENDCLASS. " 2 - Friends after definition CLASS zcl_bs_demo_private_access DEFINITION LOCAL FRIENDS ltc_private_method.
You have the option to define the friendship at the top of the test include before you define the test class (1) or to befriend it after the definition (2). Both options have advantages and disadvantages, so you can easily overlook the definition below or, as in the first case, you have to create the definition of the test class twice and announce it beforehand.
CLASS ltc_private_method IMPLEMENTATION. METHOD get_full_database. DATA(lo_cut) = NEW zcl_bs_demo_private_access( ). DATA(lt_result) = lo_cut->get_tool_data( ). cl_abap_unit_assert=>assert_not_initial( lt_result ). ENDMETHOD. ENDCLASS.
When implementing the test case, we can now access the private methods and attributes and thus test the selection individually without having to execute the output.
In some cases we may be working with completely old coding and would have to change a lot for proper testability or it cannot be produced at all. In such cases we have to completely deactivate disruptive calls and components. The easiest way to do this is with the test seam. These are two small statements with which we can achieve our goal. For the test we adapt the method “display_tools” and place the method call in the test team “display”.
METHOD display_tools. DATA(lt_tools) = get_tool_data( ). TRY. TEST-SEAM display. rd_displayed = NEW zcl_bs_demo_display( )->display_generic_data( lt_tools ). END-TEST-SEAM. CATCH cx_sy_data_access_error. rd_displayed = abap_false. ENDTRY. ENDMETHOD.
The coding is now prepared accordingly for our test cases. Now it's about the implementation of the test cases and what actually does such a test seam. We have prepared the entire test class for you.
CLASS ltc_seam DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. METHODS: no_seam FOR TESTING, empty_seam FOR TESTING, returning_seam FOR TESTING. ENDCLASS. CLASS ltc_seam IMPLEMENTATION. METHOD empty_seam. DATA(lo_cut) = NEW zcl_bs_demo_private_access( ). TEST-INJECTION display. END-TEST-INJECTION. DATA(ld_result) = lo_cut->display_tools( ). cl_abap_unit_assert=>assert_false( ld_result ). ENDMETHOD. METHOD no_seam. DATA(lo_cut) = NEW zcl_bs_demo_private_access( ). DATA(ld_result) = lo_cut->display_tools( ). cl_abap_unit_assert=>assert_false( ld_result ). ENDMETHOD. METHOD returning_seam. DATA(lo_cut) = NEW zcl_bs_demo_private_access( ). TEST-INJECTION display. rd_displayed = abap_true. END-TEST-INJECTION. DATA(ld_result) = lo_cut->display_tools( ). cl_abap_unit_assert=>assert_true( ld_result ). ENDMETHOD. ENDCLASS.
How exactly do the individual methods behave? At this point we want to break down the behavior:
- Method no_seam - No test injection is called, so the code in the method is called normally as if the test seam did not exist.
- Method emtpy_seam - The test injection in the method is empty, so the test seam in the method to be tested is also not replaced by anything. This means that the dependent class is not called. However, you should make sure that the return value is not set either.
- Method returning_seam - In the test injection, the return value for the method is set and the dependent class is no longer called. We have now completely deactivated the dependent component and the test always returns an ABAP_TRUE.
With test seams and test injections, it is easy to deactivate dependent components in the existing source code and replace them with “lighter” code. For testable code, it is not necessarily the optimal tool of choice because the code behaves differently from situation to situation. It should therefore be testable code from the outset or, in exceptional cases, apply to existing coding in order to make it testable.
With our tips, you now have additional methods and resources available to you on how you can set up your tests in order to test code more efficiently and easily.