Introduction:
AUnits are unit tests written by ABAP developers to test each functionality of their code. In RAP, AUnits are used to test functionalities such as validations, determinations, actions, and CRUD operations etc. Your AUnits should aim for maximum coverage of your code. It is recommended to follow TDD(Test Driven Development), where you write the test cases first and then implement the logic. This ensures that the test cases are based on the actual requirements and are not influenced by the implementation.
Reference link for TDD: https://help.sap.com/docs/abap-cloud/abap-development-tools-user-guide/test-driven-development-with-...
Basics of AUnits:
Test classes can be either local or global. Unit tests should typically be implemented as local classes within the program object that is being tested. However, you can use global test classes to host reusable logic for unit tests across different programs. In Eclipse ADT, there is a separate environment for managing local test classes.
Reference link: https://learning.sap.com/learning-journeys/acquire-core-abap-skills/implementing-code-tests-with-aba...
Structure of test class:
*"* use this source file for your ABAP unit test classes
class ltc_zi_lfa1_2 DEFINITION FINAL for
TESTING RISK LEVEL HARMLESS DURATION SHORT.
PRIVATE SECTION.
CLASS-DATA environment TYPE REF TO if_cds_test_environment.
DATA td_zlfa TYPE STANDARD TABLE OF zlfa1 with empty key.
DATA act_results type STANDARD TABLE OF zi_lfa1_2 WITH EMPTY KEY.
CLASS-METHODS class_setup RAISING cx_static_check.
CLASS-METHODS class_teardown.
METHODS setup RAISING cx_static_check.
METHODS prepare_testdata.
METHODS aunit_for_cds_method FOR TESTING RAISING cx_static_check.
ENDCLASS.
For Testing: The addition FOR TESTING is used to identify a test class. These are the classes that will be executed when you run ABAPUnit tests. I will explain further how AUnits are triggered and how to execute them.
Risk Level:
Duration:
Methods in Test Classes:
Good Practices:
Flow of test case:
CLASS_SETUP
This static method is executed once before the first test of the test class. Use this method for fixtures that are time-consuming to create and for which you are sure the settings will not be changed by any of the test methods. Example: Setting up a test double in the test environment.
SETUP
This instance method is called before each test of the test class. Use this method for fixtures that you want to create fresh for every test case. Example: Creating an instance of the CUT (Class Under Test).
TEARDOWN
This instance method is called after each test of the test class. Use it to undo changes made in the SETUP method. It is especially important if SETUP modifies persistent data (such as system configuration, customizing, or master data). Example: Clearing test doubles so that each test starts with fresh data.
CLASS_TEARDOWN
This static method is executed once after the last test of the test class. Use it to clean up or destroy the test environment set up in CLASS_SETUP, once all tests are completed. Example: Tearing down the overall test environment.
To run ABAP units execute ABAP Unit Test. This will trigger the test class. If you run ABAP application it will not trigger test class.
Test double framework:
As I mentioned earlier, your unit tests should check only the functionality(CUT – Code Under Test) that you have written the test case for. However, in real-world scenarios, each component is usually interlinked and not standalone.
What is the issue?
To explain this, let’s take an example. Suppose you are testing an instance authorization method. This method is dependent on an authorization object. If the AUTH-CHECK of the authorization object fails, the instance authorization method will also fail. This means that even if your instance authorization method is working correctly, the test could still fail due to its dependency. Ideally, the failure of a dependency should not cause your unit test to fail when you are testing the functionality of the CUT itself.
Examples of dependencies in RAP
Solution — Test Double Framework
So, how do we overcome this? Instead of relying on dependencies that are beyond our control, we mock or stub these dependencies. This allows us to influence how they behave and interact during testing, thereby avoiding test case failures due to external dependencies. This approach is implemented using the Test Double Framework.
The main purpose of both mock and stub is to not to test from actual data but rather a fake data that we can configure. Because our test class will fail if its dependent on real time data as it will be different in different
environment.
I will now discuss in detail how we write ABAP Unit tests with a simple example.
Requirement:
Write a positive test case to check the validation check_country, which should throw an exception if we pass a country code with more than2characters. Also, remove the dependency on the CDS view.
Steps:
Identify CUT and dependencies
Here, I have taken a validation method as an example for testing. The CUT(Code Under Test) is the validation method, and the dependency is the CDS view. So, I will use the STUB methodology to configure the response of the CDS view.
Creation of test class
Once you create the test class, you need to add it as a friend in the local handler class (since we are testing a validation). This is necessary because all the methods of the local handler class are inside the private section and cannot be accessed directly.
Inside the test class
I have already explained the use of SETUP, CLASS_SETUP, TEARDOWN, andCLASS_TEARDOWN earlier, so I will not repeat that here.
In this example, validate_check_country is our test method. I have added a test double for the CDS view, because the validation should be tested using the mock data we provide, not the actual data.
I create the mock data and insert it, so that the CDS view returns this data. Now, when we pass parameters to the validation, we can check if we get a value in reported, since we have provided a country code with 3 characters.
Note: This is just an example program. Typically, mocking, adding mock data, and different functionalities would be implemented in separate methods. It is not a good practice to combine everything into a single method. Each method should have only one responsibility. Here, I have combined them solely to showcase the concept.
class ltc_lfa1_validation IMPLEMENTATION.
METHOD class_setup.
cds_environment = cl_cds_test_environment=>create( i_for_entity = 'zi_lfa1_2' ).
ENDMETHOD.
METHOD setup.
CREATE object cut for testing.
cds_encironment->clear_doubles( ).
ENDMETHOD.
method validate_check_country.
data: lfa1_mock_data type STANDARD TABLE OF zlfa1.
data(system_uuid) = cl_uuid_factory=>create_system_uuid( ).
DATA(uuid_x16) = system_uuid->create_uuid_x16( ).
select * FROM zi_lfa1_2 into TABLE @DATA(lt_result).
lfa1_ock_data = VALUE#( (
Lifnr = uuid_x16 Name1 = 'Raj' Land1 = 'Ind' ) )
cds_environment->insert_test_data( i_data = lfa1_mock_data ).
select * from zi_lfa1_2 into TABLE @DATA(lt_result).
i_keys = value #( ( Lifnr = uuid_x16 ) ).
cut-> check_country( exporting key = i_keys
changing failed = c_failed reported = c_reported ).
cl_abap_unit_assert=>assert_not_initial( act = c_reported msg = 'failed' ).
ENDMETHOD.
method teardown.
cds_environment->destroy( ).
ENDMETHOD.
METHOD class_teardown.
cds_environment->clear_doubles( ).
ENDMETHOD.
ENDCLASS.Conclusion: Writing ABAP unit test in RAP ensures that the underling business logic is robust and reliable. Structured unit test not only improve code quality and reliability but also facilitate easier maintenance and refactoring.