USE OF TEST ENVIRONMENT IN ABAP UNIT TEST CLASS
ABAP Unit Test class, a simpler way to verify the behavior of our code. The unit test class not only check the code coverage but helps a developer to cover all possible scenarios of a code leading to more reliable code with less chances of rework.
This blog will explain the following points:
- Generation of unit test class in SE80.
- OSQL test double framework.
- Unit test case method for each scenario.
- Use of Test-Seam and Test-Injection.
- Generation of Unit Test Class in SE80:
For demo purpose, I have created one class ‘ZCL_UNIT_TEST_CLASS_DEMO1’ which is having one method ‘READ_BOM_VERSION’. There is a wizard through which we can generate a test class.
Following are the steps for generating test class:
Figure 4 : Click on create button and provide test class name. Select all options to generate predefined methods.
Figure 5 : Select the methods for which we want to generate test class.
Figure 6 : Test class generation is completed.
On completion of test class generation, local test class is generated( Figure 7), this we can modify based on our requirement.
class ltc_Unit_Test_Class_Demo1 definition for testing duration short risk level harmless. *?<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0"> *?<asx:values> *?<TESTCLASS_OPTIONS> *?<TEST_CLASS>ltc_Unit_Test_Class_Demo1 *?</TEST_CLASS> *?<TEST_MEMBER>f_Cut *?</TEST_MEMBER> *?<OBJECT_UNDER_TEST>ZCL_UNIT_TEST_CLASS_DEMO1 *?</OBJECT_UNDER_TEST> *?<OBJECT_IS_LOCAL/> *?<GENERATE_FIXTURE>X *?</GENERATE_FIXTURE> *?<GENERATE_CLASS_FIXTURE>X *?</GENERATE_CLASS_FIXTURE> *?<GENERATE_INVOCATION>X *?</GENERATE_INVOCATION> *?<GENERATE_ASSERT_EQUAL>X *?</GENERATE_ASSERT_EQUAL> *?</TESTCLASS_OPTIONS> *?</asx:values> *?</asx:abap> private section. data: f_Cut type ref to zcl_Unit_Test_Class_Demo1. "class under test class-methods: class_Setup. class-methods: class_Teardown. methods: setup. methods: teardown. methods: read_Bom_Version for testing. endclass. "ltc_Unit_Test_Class_Demo2 class ltc_Unit_Test_Class_Demo1 implementation. method class_Setup. endmethod. method class_Teardown. endmethod. method setup. create object f_Cut. endmethod. method teardown. endmethod. method read_Bom_Version. data is_Mass_Det type mpes_Majorassembly. data is_Rcnip01 type rcnip01. data ev_Bom_Ver type cs_Versn. data et_Bom_Ver type zcl_Unit_Test_Class_Demo1=>tt_Bom_Ver. f_Cut->read_Bom_Version( EXPORTING IS_MASS_DET = is_Mass_Det IS_RCNIP01 = is_Rcnip01 * IMPORTING * EV_BOM_VER = ev_Bom_Ver * ET_BOM_VER = et_Bom_Ver ). cl_Abap_Unit_Assert=>assert_Equals( act = ev_Bom_Ver exp = ev_Bom_Ver "<--- please adapt expected value " msg = 'Testing value ev_Bom_Ver' * level = ). cl_Abap_Unit_Assert=>assert_Equals( act = et_Bom_Ver exp = et_Bom_Ver "<--- please adapt expected value " msg = 'Testing value et_Bom_Ver' * level = ). endmethod. endclass.
- OSQL test double framework:
This framework is very helpful for business logic testing without any database dependency. This framework is mainly used to cover select queries in production code without any impact on database. Through this, we create a virtual environment of select queries and then mocking data into it, to check the behavior of our code.
How it works?
- First create a static attribute of type reference to interface ‘IF_OSQL_TEST_ENVIRONMENT’.
- Creates an instance of Test Environment for test execution in predefined method ‘CLASS_SETUP’ and declare all the tables in it which were used in select queries of main class methods.
- With this, we will be creating an environment for select queries in our methods and we will mock those tables in which data is coming from select query.
Figure 8 : Test environment for DB artifact redirection
- Unit test case method for each scenario:
Now, we will change unit test cases (which was generated while generating test class) for each method as per our requirement.
For example: In method ‘READ_BOM_VERSION’, there are two possible scenarios, one is highlighted as red and the other one as yellow. At a time, only one scenario will be executed and hence for this method, two unit test cases will be needed.
- Unit test case for condition: if lv_line EQ 1:
Here, we are mocking all the input parameters and the tables of select query. And we are inserting that test data in dummy environment.
Figure 10: Use of Test environment in UTC
2. Unit test case for else condition:
- Use of Test-Seam and Test-Injection:
In unit test class, we do not cover the lines of any ‘CALL FUNCTION’ and ‘CALL METHOD’ (of some other class). To skip this part, we use Test-Seam—End-Test-Seam in production code. For every Test-Seam—End-Test-Seam, there will be a corresponding Test-Injection—End-Test-Injection in unit test class.
By using Test-Seam and Test-Injection, we are avoiding the dependency of some another class or function module from our code.
If some output is expected from that call function, then we can mock it in the corresponding Test-Injection—End-Test-Injection.
Figure 12 : Use of Test-Seam–End-Test-Seam in class method.
Figure 13 : Use of Test-Injection–End-Test-Injection in UTC.
Earlier, we used to skip select queries in unit test class (to avoid database dependency) by using Test-Seam—End-Test-Seam, which leads to affect the code coverage. With OSQL environment, we create a virtual database, that not only allows us to check behavior of select queries but also help us to achieve better code coverage.
Below is a complete code of Unit Test Class :
*"* use this source file for your ABAP unit test classes CLASS ltc_unit_test_class_demo1 DEFINITION DEFERRED. CLASS zcl_unit_test_class_demo1 DEFINITION LOCAL FRIENDS ltc_unit_test_class_demo1. CLASS ltc_unit_test_class_demo1 DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. DATA: f_cut TYPE REF TO zcl_unit_test_class_demo1. "class under test CLASS-DATA : environment TYPE REF TO if_osql_test_environment. CLASS-METHODS: class_setup. CLASS-METHODS: class_teardown. METHODS: setup. METHODS: teardown. METHODS: read_bom_version_if FOR TESTING. METHODS: read_bom_version_else FOR TESTING. ENDCLASS. "ltc_Unit_Test_Class_Demo1 CLASS ltc_unit_test_class_demo1 IMPLEMENTATION. METHOD class_setup. environment = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( 'MAST' ) ( 'STZU' ) ( 'STKO' ) ) ). ENDMETHOD. METHOD class_teardown. ENDMETHOD. METHOD setup. CREATE OBJECT f_cut. ENDMETHOD. METHOD teardown. ENDMETHOD. METHOD read_bom_version_if. DATA: is_mass_det TYPE mpes_majorassembly, is_rcnip01 TYPE rcnip01, lv_bom_ver TYPE cs_versn ##NEEDED, lt_bom_ver TYPE zcl_unit_test_class_demo1=>tt_bom_ver, lt_mast_bm TYPE TABLE OF mast, lt_stzu_bm TYPE TABLE OF stzu, lt_stko_bm TYPE TABLE OF stko. is_mass_det = VALUE #( matnr = 'MAJOR_ASMBLY03' plant = '0001' stlal = '01'). is_rcnip01 = VALUE #( stlan = 'V' ). lt_mast_bm = VALUE #( ( matnr = 'MAJOR_ASMBLY03' werks = '0001' stlnr = '89876' stlan = 'V' stlal = '01' ) ). lt_stzu_bm = VALUE #( ( stlty = 'M' stlnr = '89876' versnind = 'X' ) ). lt_stko_bm = VALUE #( ( stlty = 'M' stlnr = '89876' versnst = '99' bom_versn = '0001') ). environment->insert_test_data( lt_mast_bm ). environment->insert_test_data( lt_stzu_bm ). environment->insert_test_data( lt_stko_bm ). f_cut->read_bom_version( EXPORTING is_mass_det = is_mass_det is_rcnip01 = is_rcnip01 IMPORTING ev_bom_ver = lv_bom_ver et_bom_ver = lt_bom_ver ). "Checking the method cl_abap_unit_assert=>assert_not_initial( act = lt_bom_ver msg = 'Testing Read BOM Version Failed' ). ENDMETHOD. METHOD read_bom_version_else. DATA: is_mass_det TYPE mpes_majorassembly, is_rcnip01 TYPE rcnip01, lv_bom_ver TYPE cs_versn ##NEEDED, lt_bom_ver TYPE zcl_unit_test_class_demo1=>tt_bom_ver, lt_mast TYPE TABLE OF mast, lt_stzu TYPE TABLE OF stzu, lt_stko TYPE TABLE OF stko. is_mass_det = VALUE #( matnr = 'MAJOR_ASMBLY1' plant = '0001' stlal = '01'). is_rcnip01 = VALUE #( stlan = 'V' ). lt_mast = VALUE #( ( matnr = 'MAJOR_ASMBLY1' werks = '0001' stlnr = '89878' stlan = 'V' stlal = '01' ) ( matnr = 'MAJOR_ASMBLY1' werks = '0001' stlnr = '234561' stlan = 'V' stlal = '01' ) ). lt_stzu = VALUE #( ( stlty = 'M' stlnr = '89878' versnind = 'X' ) ( stlty = 'M' stlnr = '234561' versnind = 'X' ) ). lt_stko = VALUE #( ( stlty = 'M' stlnr = '89878' versnst = '01' bom_versn = '0001') ( stlty = 'M' stlnr = '234561' versnst = '99' bom_versn = '0002') ). environment->insert_test_data( lt_mast ). environment->insert_test_data( lt_stzu ). environment->insert_test_data( lt_stko ). f_cut->read_bom_version( EXPORTING is_mass_det = is_mass_det is_rcnip01 = is_rcnip01 IMPORTING ev_bom_ver = lv_bom_ver et_bom_ver = lt_bom_ver ). "Checking the method cl_abap_unit_assert=>assert_not_initial( act = lt_bom_ver msg = 'Testing Read BOM Version Failed' ). ENDMETHOD. ENDCLASS.
Nice. Pictures always add a nice touch.
Yes. Nicely written.
I knew about the wizard, but I've never used it. I probably won't either as I use Eclipse almost exclusively now for development.
Off topic, but three things finally sold Eclipse to me:
1. Local revision history - in a session, each save generates a local version you can revert to if necessary
2. The objects I was working on + the place I was at are retained. So I can stop at the end of the day, and pick up the next day from where I left of.
3. Quick fixes.
And opening many sessions at once. is an advantage too. However, I like the debug on the GUI better than the eclipse version. Maybe I'm not looking at it enough.
This provides a great insight on the ABAP Unit Test Classes.
Great Work. Keep going.
Really Nice Blog .
This method to create virtual database for select queries is very nice.
Found it really helpful and productive.
Thank you for the post.
I agree with Devansh, of particular interest to me was the use of the OSQL test double framework.
I have been trying to move my own development towards TDD and using Unit Testing as a matter of practice. So, it is good to see other options which are available to implement unit tests. I know there are other frameworks, but sometimes it takes a post and few pictures to kick yourself into gear and explore further.
I would also agree with Matthew, using Eclipse for this style of development makes it so much easier. I use Eclipse almost exclusively for my development, but I still do from time to time bounce back to the GUI for certain processes.
Thanks for writing this wonderful blog for the ABAP Unit Tests. Unfortunately, all the above methods of test doubles are available only in SAP NetWeaver 7.5 and higher version. For the lower versions we need to use the existing techniques of mocking. One of the very popular one is using the class CL_ABAP_TESTDOUBLE.
Is there any chance that these will be down ported to the lower versions in the near future. I'm afraid that I won't get a chance to get my hands dirty in ABAP 7.5 very soon.
Thanks in advance.
ABAP Unit is available since 6.40. The Open SQL Test Double Framework is available since 7.52. The Test Seams are available since 7.50. CL_ABAP_TESTDOUBLE is available since 7.40 SP9 (before that, you may use MockA).
I also needed osql test double framework functions in 7.50, 7.40 and older systems. So I created my own custom library zsql_test_double_framework
It also lets to test SQL statements but only if you run it with methods of the library. But it works from version 7.02.
I have written documentation on github.
Hope it will be useful.
Very well articulated !!
Done very well.
However i had a doubt, can we also mock data for a DB view? if yes then how if no then what other solution except skipping the code?