diff --git a/14_ABAP_Unit_Tests.md b/14_ABAP_Unit_Tests.md index 0b5ba19..4c12f62 100644 --- a/14_ABAP_Unit_Tests.md +++ b/14_ABAP_Unit_Tests.md @@ -15,10 +15,9 @@ - [Injecting Test Doubles](#injecting-test-doubles) - [Test Seams](#test-seams) - [Creating Test Doubles Using ABAP Frameworks](#creating-test-doubles-using-abap-frameworks) - - [Example: Creating Test Doubles Using ABAP Frameworks](#example-creating-test-doubles-using-abap-frameworks) - [Running and Evaluating ABAP Unit Tests](#running-and-evaluating-abap-unit-tests) - [More Information](#more-information) - - [Executable Example](#executable-example) + - [Executable Examples](#executable-examples) This cheat sheet contains basic information about [unit testing](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenunit_test_glosry.htm) in ABAP. @@ -60,7 +59,8 @@ This cheat sheet contains basic information about [unit testing](https://help.sa Before we look at test doubles and injections, we will look at the creation of the test classes and methods to get an idea of the code skeletons (in which test doubles and injections can be implemented). Test classes ... -- are special [local](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenlocal_class_glosry.htm) or [global classes](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenglobal_class_glosry.htm) in which tests for ABAP Unit are implemented in the form of test methods. Note: In this cheat sheet, the focus is on local test classes only. +- are special [local](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenlocal_class_glosry.htm) or [global classes](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenglobal_class_glosry.htm) in which tests for ABAP Unit are implemented in the form of test methods. + - You can define a test relation between a test class or a test method and another repository object using the ABAP Doc comment `"! @testing ...`. This is demonstrated by an [executable example](#executable-examples) class. - are created in [class pools](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenclass_pool_glosry.htm) in special [test includes](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abentest_include_glosry.htm). See the *Test Classes* tab in the ADT. - can only be used as part of test runs. - are not generated in production systems, i.e. the source code of a test class is not part of the production code of its program. @@ -562,1101 +562,7 @@ Several frameworks are available and demonstrated in the example classes below. -### Example: Creating Test Doubles Using ABAP Frameworks -The example classes below illustrate how to create and inject test doubles using the mentioned ABAP frameworks. - -To run the example, follow these steps: -- First, import the ABAP cheat sheet repository. The example relies on some of its repository objects. -- Next, create two classes: `ZCL_DEMO_ABAP_UNIT_TDF` (the class under test) and `ZCL_DEMO_ABAP_UNIT_DATAPROV` (the DOC containing methods called by the first class). Copy and paste the provided code into these classes. Note that `ZCL_DEMO_ABAP_UNIT_TDF` includes a test class in the test include (*Test Classes* tab in ADT). -- The example is set up to display content in the ADT console when you run the class with *F9*. This is solely to demonstrate the effects of statements and method calls. The main focus is on test methods that explore test double creation using the frameworks. To launch the tests in the class, use *Ctrl/Cmd + Shift + F10*. - -> **💡 Note**
-> - The example classes are intentionally simplified and nonsemantic, designed to highlight basic unit tests and explore framework classes and methods. -> - They are not meant to serve as a model for proper unit test design. Always devise your own solutions for each unique case. -> - Refer to the code comments, SAP Help Portal and class documentation for additional information. - - -Expand the following collapsible section for example code. - -
- 🟢 Click to expand for example code - -
- -**Class 1 `ZCL_DEMO_ABAP_UNIT_TDF` (the class under test)** - -1a) Insert the following code in the `Global Class` tab - - -```abap -CLASS zcl_demo_abap_unit_tdf DEFINITION - PUBLIC - FINAL - CREATE PUBLIC . - - PUBLIC SECTION. - -* -------------------------- NOTE ---------------------------------- -* - This is an example class that demonstrates managing dependencies -* (dependent-on-components, DOC) with ABAP Unit. -* - Topics covered: -* - ABAP OO Test Double Framework (test doubles injected using -* constructor and parameter injection) -* - ABAP SQL Test Double Framework -* - ABAP CDS Test Double Framework -* - Managing dependencies on RAP business objects (mocking ABAP EML -* APIs, transactional buffer test doubles) -* -* ----------------------- RUN ABAP UNIT TEST--------------------------- -* - Open the class with the ABAP development tools for Eclipse (ADT). -* - Choose Ctrl/Cmd + Shift + F10 to launch all tests in the class. -* You can also right-click in the class and choose Run as -> ABAP Unit -* Test. -* - The results of a test run are displayed in the ABAP Unit tab in ADT -* and can be evaluated. -* -* ----------------------- RUN CLASS ----------------------------- -* - Open the class with the ABAP development tools for Eclipse (ADT). -* - Choose F9 to run the class and check the console output. - - INTERFACES if_oo_adt_classrun. - - "--------------- 1) ABAP OO Test Double Framework --------------- - "The examples in the test include use the cl_abap_testdouble class. - - "----------- 1a) Demonstrating constructor injection ----------- - - "Specifying the instance constructor with an optional parameter - "for the purpose of constructor injection as injection mechanism. - "In this example, methods are called from another, non-final class. - "They represent DOCs. Test doubles are injected when running unit - "tests. The parameter expects an instance of this class. - METHODS constructor - IMPORTING oref_constr_inj TYPE REF TO zcl_demo_abap_unit_dataprov OPTIONAL. - - "Declaring an object reference variable that will be used to call - "methods of an external class (the DOC) - DATA oref_data_provider TYPE REF TO zcl_demo_abap_unit_dataprov. - - "Method that is tested and contains a DOC. In this case, it is an - "external method that is called in the implementation part. The DOC - "is replaced by a test double created using cl_abap_testdouble. - METHODS td_constr_inj_calc_discount - IMPORTING - value TYPE numeric - RETURNING - VALUE(result) TYPE decfloat34. - - "----------- 1b) Demonstrating parameter injection ----------- - - "Method that is tested and contains a DOC. In this case, it is an - "external method that is called in the implementation part. The DOC - "is replaced by a test double created using cl_abap_testdouble. - "The example demonstrates parameter injection. Therefore, an - "optional importing parameter is included. When running the unit - "test, 'data_prov' is bound, and the test double is injected. - METHODS td_param_inj_calc_discount - IMPORTING value TYPE numeric - date TYPE d - time TYPE t - data_prov TYPE REF TO zcl_demo_abap_unit_dataprov OPTIONAL - EXPORTING message TYPE string - weekday TYPE string - RETURNING VALUE(result) TYPE decfloat34. - - "----------- 2) ABAP SQL Test Double Framework ----------- - "The examples in the test include use the cl_osql_test_environment - "class. Here, a database table represents the DOC. - "The method includes a SELECT statement. - - METHODS sql_get_shortest_flight_time IMPORTING carrier TYPE zdemo_abap_flsch-carrid - RETURNING VALUE(shortest_flight) TYPE i. - - "----------- 3) ABAP CDS Test Double Framework ----------- - "The examples in the test include use the cl_cds_get_data_set_environment class. - "Here, a CDS view entity represents the DOC. The method includes a SELECT - "statement. Another test method is implemented in the test inlcude that - "demonstrates the testing of a CDS view entity (without testing a method in the - "class under test). - METHODS cds_get_data_set IMPORTING carrier TYPE zdemo_abap_cds_ve_agg_exp-carrid - RETURNING VALUE(agg_line) TYPE zdemo_abap_cds_ve_agg_exp. - - "----------- 4) Managing dependencies on RAP business objects ----------- - "----------- 4a) Demonstrating mocking ABAP EML APIs -------------------- - "----------- ABAP EML read operations ----------------------------------- - "The examples in the test include use the cl_botd_mockemlapi_bo_test_env class. - - "Populating database tables for ABAP EML read requests - METHODS prep_dbtab_for_eml IMPORTING read_op TYPE abap_boolean DEFAULT abap_false. - TYPES read_tab_ro TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m. - TYPES read_tab_ch TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m\_child. - - "Method that includes an ABAP EML read request on the root entity - METHODS eml_read_root IMPORTING key TYPE zdemo_abap_rap_ro_m-key_field - RETURNING VALUE(tab_ro) TYPE read_tab_ro. - - "Method that includes an ABAP EML read-by-associaton request - METHODS eml_rba IMPORTING key TYPE zdemo_abap_rap_ro_m-key_field - RETURNING VALUE(tab_ch) TYPE read_tab_ch. - - "----------- ABAP EML modify operation ----------- - TYPES modify_tab_ro TYPE TABLE FOR CREATE zdemo_abap_rap_ro_m. - TYPES ty_mapped TYPE RESPONSE FOR MAPPED EARLY zdemo_abap_rap_ro_m. - TYPES ty_failed TYPE RESPONSE FOR FAILED EARLY zdemo_abap_rap_ro_m. - TYPES ty_reported TYPE RESPONSE FOR REPORTED EARLY zdemo_abap_rap_ro_m. - - "Method that includes an ABAP EML modify request on the root entity - METHODS eml_modify_root IMPORTING VALUE(instances) TYPE modify_tab_ro - EXPORTING mapped TYPE ty_mapped - failed TYPE ty_failed - reported TYPE ty_reported. - - "----------- 4b) Demonstrating transactional buffer test doubles --------- - "The examples in the test include use the cl_botd_txbufdbl_bo_test_env class. - TYPES read_tab_ro_u TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_u. - - "Method that includes an ABAP EML read request on the root entity - METHODS eml_read_root_buffer_td IMPORTING key TYPE zdemo_abap_rap_ro_u-key_field - RETURNING VALUE(tab_ro_u) TYPE read_tab_ro_u. - - PROTECTED SECTION. - PRIVATE SECTION. - -ENDCLASS. - - - -CLASS zcl_demo_abap_unit_tdf IMPLEMENTATION. - - METHOD if_oo_adt_classrun~main. - "This implementation includes method calls of those methods that are unit tested - "in the test include. - "The implementation is just for demonstration purposes to explore the effect of - "the methods. - - "Populating demo database tables (only required for running the example class - "with F9 and exploring the effect of various method calls) - zcl_demo_abap_aux=>fill_dbtabs( ). - - "----- 1) Methods demonstrating the ABAP OO Test Double Framework in the test include ----- - "----- 1a) Using constructor injection ----- - DATA(td_constr_inj_calc_discount) = td_constr_inj_calc_discount( 100 ). - out->write( data = td_constr_inj_calc_discount name = `td_constr_inj_calc_discount` ). - out->write( |\n| ). - - td_constr_inj_calc_discount = td_constr_inj_calc_discount( 500 ). - out->write( data = td_constr_inj_calc_discount name = `td_constr_inj_calc_discount` ). - out->write( |\n| ). - - td_constr_inj_calc_discount = td_constr_inj_calc_discount( CONV decfloat34( '3.4' ) ). - out->write( data = td_constr_inj_calc_discount name = `td_constr_inj_calc_discount` ). - out->write( |\n| ). - - "----- 1a) Using parameter injection ----- - "Example with Sunday - td_param_inj_calc_discount( - EXPORTING - value = 100 - date = '20241201' - time = '100000' - IMPORTING - weekday = DATA(weekday) - message = DATA(message) - RECEIVING - result = DATA(td_param_inj_calc_discount) - ). - - out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ). - out->write( data = weekday name = `weekday` ). - out->write( data = message name = `message` ). - out->write( |\n| ). - - "Example with a weekday, morning - td_param_inj_calc_discount( - EXPORTING - value = 100 - date = '20241202' - time = '100000' - IMPORTING - weekday = weekday - message = message - RECEIVING - result = td_param_inj_calc_discount - ). - - out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ). - out->write( data = weekday name = `weekday` ). - out->write( data = message name = `message` ). - out->write( |\n| ). - - "Example with a weekday, afternoon - td_param_inj_calc_discount( - EXPORTING - value = 100 - date = '20241203' - time = '150000' - IMPORTING - weekday = weekday - message = message - RECEIVING - result = td_param_inj_calc_discount - ). - - out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ). - out->write( data = weekday name = `weekday` ). - out->write( data = message name = `message` ). - out->write( |\n| ). - - "Example with a weekday, night - td_param_inj_calc_discount( - EXPORTING - value = 100 - date = '20241204' - time = '230000' - IMPORTING - weekday = weekday - message = message - RECEIVING - result = td_param_inj_calc_discount - ). - - out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ). - out->write( data = weekday name = `weekday` ). - out->write( data = message name = `message` ). - out->write( |\n| ). - - "----- 2) Methods demonstrating the ABAP SQL Test Double Framework in the test include ----- - DATA(sql_shortest_flight_time) = sql_get_shortest_flight_time( 'LH' ). - out->write( data = sql_shortest_flight_time name = `sql_shortest_flight_time` ). - out->write( |\n| ). - - sql_shortest_flight_time = sql_get_shortest_flight_time( 'AA' ). - out->write( data = sql_shortest_flight_time name = `sql_shortest_flight_time` ). - out->write( |\n| ). - - "----- 3) Methods demonstrating the ABAP CDS Test Double Framework in the test include ----- - DATA(data_set_cds) = cds_get_data_set( 'LH' ). - out->write( data = data_set_cds name = `data_set_cds` ). - out->write( |\n\n| ). - - data_set_cds = cds_get_data_set( 'AA' ). - out->write( data = data_set_cds name = `data_set_cds` ). - out->write( |\n\n| ). - - "----- Methods demonstrating mocking ABAP EML APIs in the test include ----- - "Populating demo database tables - prep_dbtab_for_eml( read_op = abap_true ). - - DATA(tab_ro) = eml_read_root( key = 1 ). - out->write( tab_ro ). - out->write( |\n\n| ). - - DATA(tab_ch) = eml_rba( key = 1 ). - out->write( tab_ch ). - out->write( |\n\n| ). - - prep_dbtab_for_eml( ). - - eml_modify_root( - EXPORTING - instances = VALUE #( ( %cid = `cid1` key_field = 3 field1 = 'aaa' field2 = 'bbb' field3 = 30 field4 = 300 ) - ( %cid = `cid2` key_field = 4 field1 = 'ccc' field2 = 'ddd' field3 = 40 field4 = 400 ) ) - IMPORTING - mapped = DATA(m) - failed = DATA(f) - reported = DATA(r) - ). - - ASSERT f IS INITIAL. - ASSERT r IS INITIAL. - out->write( data = m-root name = `m-root` ). - out->write( |\n\n| ). - - SELECT * FROM zdemo_abap_rapt1 INTO TABLE @DATA(itab). - out->write( data = itab name = `itab` ). - ENDMETHOD. - - METHOD constructor. - "--------------------- 1a) --------------------- - "Demonstrating the constructor injection - "The parameter is only bound when you run the unit test. - "In that case, the test double is injected, and method calls - "in the implementations use data from the test double. - "Otherwise, a new instance is created that is used to call - "methods (a test double is not injected). - IF oref_constr_inj IS BOUND. - oref_data_provider = oref_constr_inj. - ELSE. - oref_data_provider = NEW #( ). - ENDIF. - ENDMETHOD. - - METHOD td_constr_inj_calc_discount. - "--------------------- 1a) --------------------- - "Method that demonstrates the ABAP OO Test Double Framework and - "constructor injection in ABAP Unit. - "When running the unit test, 'oref_data_provider' includes the test - "double. The method expects a numeric value. 'get_discount' returns - "another numeric value on whose basis a discount calculation is - "performed. The value returned by the 'get_discount' method depends - "on the current weekday and the UTC time. - result = ( value * oref_data_provider->get_discount( ) ) / 100. - result = value - result. - ENDMETHOD. - - METHOD td_param_inj_calc_discount. - "--------------------- 1b) --------------------- - "Method that demonstrates the ABAP OO Test Double Framework and - "parameter injection in ABAP Unit. - "When running the unit test, the optional parameter 'data_prov' is - "assigned, and therefore bound here. 'oref_data_provider' then includes - "the test double. - "The purpose of this method is similar to 'td_constr_inj_calc_discount'. - "Here, the method expects a date and time besides a numeric value. - IF data_prov IS BOUND. - oref_data_provider = data_prov. - ENDIF. - - "Getting the weekday - DATA(day_value) = ( 5 + date MOD 7 ) MOD 7 + 1. - weekday = SWITCH #( day_value - WHEN 1 THEN `Monday` - WHEN 2 THEN `Tuesday` - WHEN 3 THEN `Wednesday` - WHEN 4 THEN `Thursday` - WHEN 5 THEN `Friday` - WHEN 6 THEN `Saturday` - WHEN 7 THEN `Sunday` ). - - DATA(time_value) = COND #( WHEN time BETWEEN '000000' AND '045959' THEN 1 "Night discount - WHEN time BETWEEN '220000' AND '235959' THEN 1 "Night discount - WHEN time BETWEEN '050000' AND '115959' THEN 2 "Morning discount - WHEN time BETWEEN '180000' AND '215959' THEN 3 "Evening discount - ELSE 0 "No discount - ). - - weekday = |{ weekday } ({ SWITCH #( time_value WHEN 1 THEN `night` WHEN 2 THEN `morning` WHEN 3 THEN `evening` ELSE `afternoon` ) })|. - - DATA(disc) = oref_data_provider->get_discount_value( day_value = day_value time_value = time_value ). - result = ( value * disc ) / 100. - result = value - result. - message = |Original value: { value }; discount: { disc }; value with discount: { result }; | && - |when: { weekday }, { date DATE = ISO }, { time TIME = ISO }|. - ENDMETHOD. - - METHOD sql_get_shortest_flight_time. - "--------------------- 2) --------------------- - "When running the unit test, the DOC (database table) is replaced with a test double. - - "Getting the shortest flight time among a given carrier - SELECT MIN( fltime ) AS fltime FROM zdemo_abap_flsch WHERE carrid = @carrier INTO @shortest_flight. - ENDMETHOD. - - METHOD cds_get_data_set. - "--------------------- 3) --------------------- - "When running the unit test, the DOC (CDS view entity) is replaced with a test double. - - "Getting a line filtered by the carrier - SELECT SINGLE * FROM zdemo_abap_cds_ve_agg_exp WHERE carrid = @carrier INTO @agg_line. - ENDMETHOD. - - METHOD prep_dbtab_for_eml. - "Preparing database tables for ABAP EML statements - DELETE FROM zdemo_abap_rapt1. - - IF read_op = abap_true. - DELETE FROM zdemo_abap_rapt2. - INSERT zdemo_abap_rapt1 FROM TABLE @( VALUE #( ( key_field = 1 field1 = 'aaa' field2 = 'bbb' field3 = 10 field4 = 100 ) ) ). - INSERT zdemo_abap_rapt2 FROM TABLE @( VALUE #( ( key_field = 1 key_ch = 11 field_ch1 = 'ccc' field_ch2 = 111 ) - ( key_field = 1 key_ch = 12 field_ch1 = 'ddd' field_ch2 = 112 ) ) ). - ENDIF. - ENDMETHOD. - - METHOD eml_read_root. - "--------------------- 4a) --------------------- - "Method that includes an ABAP EML read request on the root entity - "When running the unit test, the ABAP EML API is mocked. - - READ ENTITIES OF zdemo_abap_rap_ro_m - ENTITY root - ALL FIELDS WITH VALUE #( ( key_field = key ) ) - RESULT tab_ro. - ENDMETHOD. - - METHOD eml_rba. - "--------------------- 4a) --------------------- - "Method that includes an ABAP EML read-by-assocation request - "When running the unit test, the ABAP EML API is mocked. - - READ ENTITIES OF zdemo_abap_rap_ro_m - ENTITY root - BY \_child - ALL FIELDS WITH VALUE #( ( key_field = key ) ) - RESULT tab_ch. - ENDMETHOD. - - METHOD eml_modify_root. - "--------------------- 4a) --------------------- - "Method that includes an ABAP EML create request - "When running the unit test, the ABAP EML API is mocked. - - MODIFY ENTITIES OF zdemo_abap_rap_ro_m - ENTITY root - CREATE - FIELDS ( key_field field1 field2 field3 field4 ) - WITH instances - MAPPED mapped - FAILED failed - REPORTED reported. - - COMMIT ENTITIES. - ENDMETHOD. - - METHOD eml_read_root_buffer_td. - "--------------------- 4b) --------------------- - "Method that includes an ABAP EML read request - "When running the unit test, a transactional buffer test double is - "included. - - READ ENTITIES OF zdemo_abap_rap_ro_u - ENTITY root - ALL FIELDS WITH VALUE #( ( key_field = key ) ) - RESULT tab_ro_u. - ENDMETHOD. - -ENDCLASS. -``` - -1b) Insert the following code in the `Test Classes` tab (it is a local test class of `ZCL_DEMO_ABAP_UNIT_TDF`) - -```abap -CLASS ltcl_test DEFINITION FINAL FOR TESTING - DURATION SHORT - RISK LEVEL HARMLESS. - - PRIVATE SECTION. - - "------------------- Local test class ------------------- - - "Object reference variable for class under test - CLASS-DATA cut TYPE REF TO zcl_demo_abap_unit_tdf. - - "----- 1) ABAP OO Test Double Framework ------ - "Test method that demonstrates constructor injection - METHODS test_tdf_constr_inj_get_discnt FOR TESTING. - - "Test method that demonstrates parameter injection - METHODS test_tdf_param_inj_get_discnt FOR TESTING. - - "----------- 2) ABAP SQL Test Double Framework ----------- - METHODS test_sql_get_shortest_flight FOR TESTING RAISING cx_static_check. - CLASS-DATA sql_env TYPE REF TO if_osql_test_environment. - - "----------- 3) ABAP CDS Test Double Framework----------- - METHODS test_select_cds FOR TESTING RAISING cx_static_check. - METHODS test_cds_standalone FOR TESTING RAISING cx_static_check. - METHODS prepare_testdata_set_cds. - CLASS-DATA cds_env TYPE REF TO if_cds_test_environment. - DATA zdemo_abap_fli_tab TYPE TABLE OF zdemo_abap_fli WITH EMPTY KEY. - - "----------- 4) Managing dependencies on RAP business objects ----------- - CONSTANTS entity TYPE abp_entity_name VALUE 'ZDEMO_ABAP_RAP_RO_M'. - CLASS-DATA eml_env TYPE REF TO if_botd_mockemlapi_bo_test_env. - "--- 4a) Test methods that demonstrate mocking EML APIs --- - METHODS test_eml_read_root FOR TESTING. - METHODS test_eml_rba FOR TESTING. - METHODS test_eml_modify_root FOR TESTING. - DATA td_eml TYPE REF TO if_botd_mockemlapi_test_double. - - "--- 4b) Test method that demonstrates transactional buffer test doubles --- - METHODS test_eml_read_root_buffer_td FOR TESTING. - - CLASS-DATA buffer_env TYPE REF TO if_botd_txbufdbl_bo_test_env. - - "----------- Test fixture ----------- - "Creating a common start state for each test method, clearing doubles - METHODS setup RAISING cx_static_check. - "Includes the creation of test environments - CLASS-METHODS class_setup RAISING cx_static_check. - "Clearing generated test doubles - CLASS-METHODS class_teardown. -ENDCLASS. - - -CLASS ltcl_test IMPLEMENTATION. - - METHOD class_setup. - "Creating an instance for the class under test - cut = NEW zcl_demo_abap_unit_tdf( ). - - "Creating instances of test environments for the test execution - "------ 2) SQL ------ - sql_env = cl_osql_test_environment=>create( - i_dependency_list = VALUE #( ( 'zdemo_abap_flsch' ) ) ). - - "------ 3) CDS ------ - cds_env = cl_cds_test_environment=>create( i_for_entity = 'zdemo_abap_cds_ve_agg_exp' - test_associations = 'X' ). - - "------ 4) ABAP EML ------ - "4a) Mocking ABAP EML APIs - eml_env = cl_botd_mockemlapi_bo_test_env=>create( - environment_config = cl_botd_mockemlapi_bo_test_env=>prepare_environment_config( - )->set_bdef_dependencies( bdef_dependencies = VALUE #( ( 'ZDEMO_ABAP_RAP_RO_M' ) ) ) ). - - "4b) Transactional buffer test doubles - buffer_env = cl_botd_txbufdbl_bo_test_env=>create( - environment_config = cl_botd_txbufdbl_bo_test_env=>prepare_environment_config( - )->set_bdef_dependencies( bdef_dependencies = VALUE #( ( 'ZDEMO_ABAP_RAP_RO_U' ) ) ) ). - - ENDMETHOD. - - METHOD class_teardown. - "Clearing generated test double at the end of the test execution - sql_env->destroy( ). - cds_env->destroy( ). - eml_env->destroy( ). - buffer_env->destroy( ). - ENDMETHOD. - - METHOD setup. - sql_env->clear_doubles( ). - cds_env->clear_doubles( ). - eml_env->clear_doubles( ). - buffer_env->clear_doubles( ). - ENDMETHOD. - - METHOD test_sql_get_shortest_flight. - "--- 2) --- - "Preparing and inserting test data - DATA test_data TYPE TABLE OF zdemo_abap_flsch WITH EMPTY KEY. - test_data = VALUE #( - ( carrid = 'LH' - connid = 0401 - fltime = 435 ) - ( carrid = 'LH' - connid = 0402 - fltime = 455 ) - ( carrid = 'LH' - connid = 2402 - fltime = 65 ) ). - - sql_env->insert_test_data( test_data ). - - "Calling method of the class under test - DATA carrier TYPE zdemo_abap_flsch-carrid VALUE 'LH'. - DATA(result) = cut->sql_get_shortest_flight_time( carrier ). - - "Verifying result - cl_abap_unit_assert=>assert_equals( - act = result - exp = 65 - msg = `Not the shortest flight` - quit = if_abap_unit_constant=>quit-no ). - ENDMETHOD. - - METHOD prepare_testdata_set_cds. - "Preparing and inserting test data - zdemo_abap_fli_tab = VALUE #( - ( carrid = 'XX' - connid = 0407 - fldate = '20231128' - price = '1102.77' - currency = 'JPY' - planetype = 'A380-800' - seatsmax = 475 - seatsocc = 458 - paymentsum = '563231.65' - seatsmax_b = 30 - seatsocc_b = 27 - seatsmax_f = 20 - seatsocc_f = 20 ) - ( carrid = 'XX' - connid = 0407 - fldate = '20231019' - price = '1102.77' - currency = 'JPY' - planetype = 'A380-800' - seatsmax = 475 - seatsocc = 452 - paymentsum = '553552.12' - seatsmax_b = 30 - seatsocc_b = 28 - seatsmax_f = 20 - seatsocc_f = 19 ) - ( carrid = 'XX' - connid = 0408 - fldate = '20231128' - price = '1102.77' - currency = 'JPY' - planetype = '747-400' - seatsmax = 385 - seatsocc = 365 - paymentsum = '470129.20' - seatsmax_b = 31 - seatsocc_b = 28 - seatsmax_f = 21 - seatsocc_f = 20 ) - ( carrid = 'XX' - connid = 0408 - fldate = '20230123' - price = '1102.77' - currency = 'JPY' - planetype = '747-400' - seatsmax = 385 - seatsocc = 372 - paymentsum = '487715.90' - seatsmax_b = 31 - seatsocc_b = 31 - seatsmax_f = 21 - seatsocc_f = 20 ) ). - - cds_env->insert_test_data( i_data = zdemo_abap_fli_tab ). - ENDMETHOD. - - METHOD test_select_cds. - "--- 3) --- - "Preparing and inserting test data - prepare_testdata_set_cds( ). - - "Calling method of the class under test - DATA carrier TYPE zdemo_abap_fli-carrid VALUE 'XX'. - DATA(act_result) = cut->cds_get_data_set( carrier ). - - "Verifying result - cl_abap_unit_assert=>assert_equals( - act = act_result - exp = VALUE zdemo_abap_cds_ve_agg_exp( carrid = 'XX' currency = 'JPY' avg_seats_occ = '411.75' - avg_paysum = '518657.22' total_paysum = '2074628.87' - min_occ_seats = 365 max_occ_seats = 458 - max_occ_seats_all = 458 cnt = 4 cnt_planetype = 2 ) - msg = `The values do not match the expected result` - quit = if_abap_unit_constant=>quit-no ). - ENDMETHOD. - - METHOD test_eml_read_root. - "--- 4a) --- - "Mocking ABAP EML API (read request) - - "Preparing test data (RAP BO instances) - DATA read_tab_ro_import TYPE TABLE FOR READ IMPORT zdemo_abap_rap_ro_m. - DATA read_tab_ro_result TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m. - read_tab_ro_import = VALUE #( ( key_field = 2 ) ). - read_tab_ro_result = VALUE #( ( key_field = 2 field1 = 'uuu' field2 = 'vvv' field3 = 20 field4 = 200 ) ). - - "Configuring input and output for the ABAP EML read request - "Creating input/output configuration builders - DATA(input_config_read) = cl_botd_mockemlapi_bldrfactory=>get_input_config_builder( )->for_read( ). - DATA(output_config_read) = cl_botd_mockemlapi_bldrfactory=>get_output_config_builder( )->for_read( ). - - "Creating input for the entities (here, only one entity is included) - "Instead of the entity name, an alias name is also accepted. - DATA(eml_read_input) = input_config_read->build_entity_part( entity - )->set_instances_for_read( read_tab_ro_import ). - - "Input configuration - DATA(input) = input_config_read->build_input_for_eml( )->add_entity_part( eml_read_input ). - - "Output configuration - DATA(output) = output_config_read->build_output_for_eml( )->set_result_for_read( read_tab_ro_result ). - - "Configuring the RAP BO test double - td_eml = eml_env->get_test_double( entity ). - td_eml->configure_call( )->for_read( )->when_input( input )->then_set_output( output ). - - "Calling method (containing the ABAP EML read request) of the class under test - DATA(t_read_res_ro) = cut->eml_read_root( key = 2 ). - - "Verifying the result - cl_abap_unit_assert=>assert_equals( - act = lines( t_read_res_ro ) - exp = 1 - msg = `The number of lines does not match` - quit = if_abap_unit_constant=>quit-no ). - - TYPES struc_read_ro TYPE STRUCTURE FOR READ RESULT zdemo_abap_rap_ro_m. - cl_abap_unit_assert=>assert_equals( - act = t_read_res_ro[ 1 ] - exp = VALUE struc_read_ro( key_field = 2 field1 = 'uuu' field2 = 'vvv' field3 = 20 field4 = 200 ) - msg = `The values do not match the expected result` - quit = if_abap_unit_constant=>quit-no ). - ENDMETHOD. - - METHOD test_tdf_constr_inj_get_discnt. - "--- 1a) --- - "Method that demonstrates the ABAP OO Test Double Framework - "Injection mechanism: Constructor injection - - "Creating a test double - DATA(test_double) = CAST zcl_demo_abap_unit_dataprov( - cl_abap_testdouble=>create( 'zcl_demo_abap_unit_dataprov' ) ). - - "Configuring a method call - "It is used for the next method call (the actual method calling) - "In this example, the method only has a returning parameter. Check - "the class documentation for more static methods of the cl_abap_testdouble - "class, e.g. for configuring other parameters. - cl_abap_testdouble=>configure_call( test_double - )->returning( 5 ). - - "Calling method of the external class - test_double->get_discount( ). - - "Injecting the test double - "Here, the instance constructor is provided with the test double - "instance - cut = NEW #( test_double ). - - "Calling method of the class under test - DATA(result) = cut->td_constr_inj_calc_discount( 500 ). - - "Verifying the result - cl_abap_unit_assert=>assert_equals( - act = result - exp = 475 - msg = `The values do not match the expected result` - quit = if_abap_unit_constant=>quit-no ). - ENDMETHOD. - - METHOD test_tdf_param_inj_get_discnt. - "--- 1b) --- - "Method that demonstrates the ABAP OO Test Double Framework - "Injection mechanism: Parameter injection - - "Creating a test double - DATA(test_double_param_inj) = CAST zcl_demo_abap_unit_dataprov( - cl_abap_testdouble=>create( 'zcl_demo_abap_unit_dataprov' ) ). - - "Configuring a method call, ignoring importing parameters - cl_abap_testdouble=>configure_call( test_double_param_inj - )->ignore_parameter( 'DAY_VALUE' - )->ignore_parameter( 'TIME_VALUE' - )->returning( 40 ). - - "Calling method of the external class - "Dummy values are provided for the non-optional importing - "parameters (the returning value is determined above) - test_double_param_inj->get_discount_value( - EXPORTING - day_value = 99 - time_value = 99 - ). - - "Calling method of the class under test - "In this case, the optional parameter is assigned the - "test double replacing the DOC - cut->td_param_inj_calc_discount( - EXPORTING - value = 100 - date = '20241201' "Sunday - time = '100000' - data_prov = test_double_param_inj - IMPORTING - message = DATA(msg) - weekday = DATA(weekday) - RECEIVING - result = DATA(result) - ). - - "Verifying the result - cl_abap_unit_assert=>assert_equals( - act = result - exp = 60 - msg = `The values do not match the expected result` - quit = if_abap_unit_constant=>quit-no ). - ENDMETHOD. - - METHOD test_eml_rba. - "--- 4a) --- - "Method that demonstrates mocking an ABAP EML API (read-by-association request) - - "Preparing test data (RAP BO instances) - DATA read_tab_ch_import TYPE TABLE FOR READ IMPORT zdemo_abap_rap_ro_m\_child. - DATA read_tab_ch_result TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m\_child. - - read_tab_ch_import = VALUE #( ( key_field = 2 ) ). - - read_tab_ch_result = VALUE #( ( key_field = 2 key_ch = 22 field_ch1 = 'www' field_ch2 = 222 ) - ( key_field = 2 key_ch = 23 field_ch1 = 'xxx' field_ch2 = 223 ) ). - - "Configuring input and output for the ABAP EML read-by-association request - DATA(input_config_rba) = cl_botd_mockemlapi_bldrfactory=>get_input_config_builder( )->for_read( ). - DATA(output_config_rba) = cl_botd_mockemlapi_bldrfactory=>get_output_config_builder( )->for_read( ). - - "Creating input for the entities (here, only one entity is included) - DATA(eml_rba_input) = input_config_rba->build_entity_part( entity - )->set_instances_for_read_ba( read_tab_ch_import ). - - "Input configuration - DATA(input_rba) = input_config_rba->build_input_for_eml( )->add_entity_part( eml_rba_input ). - - "Output configuration - DATA(output_rba) = output_config_rba->build_output_for_eml( - )->set_result_for_read_ba( - source_entity_name = entity - assoc_name = '_CHILD' - result = read_tab_ch_result ). - - "Configuring the RAP BO test double - td_eml = eml_env->get_test_double( entity ). - td_eml->configure_call( )->for_read( )->when_input( input_rba )->then_set_output( output_rba ). - - "Calling method (containing the ABAP EML read-by-association request) - "of the class under test - DATA(t_read_res_ch) = cut->eml_rba( key = 2 ). - - "Verifying the result - cl_abap_unit_assert=>assert_equals( - act = lines( t_read_res_ch ) - exp = 2 - msg = `The number of lines does not match` - quit = if_abap_unit_constant=>quit-no ). - - TYPES struc_read_ch TYPE STRUCTURE FOR READ RESULT zdemo_abap_rap_ro_m\_child. - cl_abap_unit_assert=>assert_equals( - act = t_read_res_ch[ 2 ] - exp = VALUE struc_read_ch( key_field = 2 key_ch = 23 field_ch1 = 'xxx' field_ch2 = 223 ) - msg = `The values do not match the expected result` - quit = if_abap_unit_constant=>quit-no ). - ENDMETHOD. - - - METHOD test_eml_modify_root. - "--- 4a) --- - "Method that demonstrates mocking an ABAP EML API (create request) - - "Data object delcarations used in the test method - DATA create_tab_ro TYPE TABLE FOR CREATE zdemo_abap_rap_ro_m. - DATA mapped_resp TYPE RESPONSE FOR MAPPED EARLY zdemo_abap_rap_ro_m. - DATA failed_resp TYPE RESPONSE FOR FAILED EARLY zdemo_abap_rap_ro_m. - DATA reported_resp TYPE RESPONSE FOR REPORTED EARLY zdemo_abap_rap_ro_m. - - "Preparing test data (RAP BO instances, response parameters) - create_tab_ro = VALUE #( ( %cid = `cid1` key_field = 3 field1 = 'aaa' field2 = 'bbb' field3 = 30 field4 = 300 ) - ( %cid = `cid2` key_field = 4 field1 = 'ccc' field2 = 'ddd' field3 = 40 field4 = 400 ) ). - - mapped_resp-root = VALUE #( ( %cid = 'cid1' key_field = 3 ) ( %cid = 'cid2' key_field = 4 ) ). - failed_resp-root = VALUE #( ). - reported_resp-root = VALUE #( ). - - "Configuring input and output for the ABAP EML create request - DATA(input_config_modify) = cl_botd_mockemlapi_bldrfactory=>get_input_config_builder( )->for_modify( ). - DATA(output_config_modify) = cl_botd_mockemlapi_bldrfactory=>get_output_config_builder( )->for_modify( ). - - "Creating input for the entities (here, only one entity is included) - DATA(eml_modify_input) = input_config_modify->build_entity_part( entity - )->set_instances_for_create( create_tab_ro ). - - "Input configuration - DATA(input) = input_config_modify->build_input_for_eml( )->add_entity_part( eml_modify_input ). - - "Output configuration - DATA(output) = output_config_modify->build_output_for_eml( )->set_mapped( mapped_resp - )->set_failed( failed_resp )->set_reported( reported_resp ). - - "Configuring the RAP BO test double - td_eml = eml_env->get_test_double( entity ). - td_eml->configure_call( )->for_modify( )->when_input( input )->then_set_output( output ). - - "Calling method (containing the ABAP EML read request) of the class under test - cut->eml_modify_root( - EXPORTING - instances = VALUE #( ( %cid = `cid1` key_field = 3 field1 = 'aaa' field2 = 'bbb' field3 = 30 field4 = 300 ) - ( %cid = `cid2` key_field = 4 field1 = 'ccc' field2 = 'ddd' field3 = 40 field4 = 400 ) ) - IMPORTING - mapped = DATA(m) - failed = DATA(f) - reported = DATA(r) - ). - - "Verifying the result - cl_abap_unit_assert=>assert_equals( - act = lines( m-root ) - exp = 2 - msg = `The number of lines does not match` - quit = if_abap_unit_constant=>quit-no ). - - cl_abap_unit_assert=>assert_equals( - act = lines( f-root ) - exp = 0 - msg = `The number of lines does not match` - quit = if_abap_unit_constant=>quit-no ). - - cl_abap_unit_assert=>assert_equals( - act = lines( r-root ) - exp = 0 - msg = `The number of lines does not match` - quit = if_abap_unit_constant=>quit-no ). - - ENDMETHOD. - - METHOD test_eml_read_root_buffer_td. - "--- 4b) --- - "Method that demonstrates transactional buffer test doubles - - "Creating test double - "Note: You have more configuration options. Check, for example, the - "methods configure_additional_behavior, set_fields_handler methods, etc. - "as described in the documentation. - DATA(double) = buffer_env->get_test_double( 'zdemo_abap_rap_ro_u' ). - - "Preparing test data - "Populating the transactional buffer with an ABAP EML create request - MODIFY ENTITIES OF zdemo_abap_rap_ro_u - ENTITY root - CREATE FIELDS ( key_field field1 field2 field3 field4 ) - WITH VALUE #( ( %cid = `cid5` key_field = 5 field1 = 'eee' ) - ( %cid = `cid6` key_field = 6 field1 = 'fff' ) ) - REPORTED DATA(reported) - FAILED DATA(failed) - MAPPED DATA(mapped). - - "Calling method (containing an ABAP EML read request) of the class under test - cut->eml_read_root_buffer_td( - EXPORTING - key = 5 - RECEIVING - tab_ro_u = DATA(read_result) ). - - "Verifying the result - cl_abap_unit_assert=>assert_equals( - act = read_result[ 1 ]-key_field - exp = 5 - msg = `The value does not match the expected result` - quit = if_abap_unit_constant=>quit-no ). - - cl_abap_unit_assert=>assert_equals( - act = read_result[ 1 ]-field1 - exp = 'eee' - msg = `The value does not match the expected result` - quit = if_abap_unit_constant=>quit-no ). - - "Another calling of the method of the class under test (non-existent instance - "in the transactional buffer test double) - cut->eml_read_root_buffer_td( - EXPORTING - key = 1 - RECEIVING - tab_ro_u = DATA(read_result_f) ). - - cl_abap_unit_assert=>assert_initial( - EXPORTING - act = read_result_f - msg = `An instance was found` - quit = if_abap_unit_constant=>quit-no ). - - ENDMETHOD. - - METHOD test_cds_standalone. - "--- 3) --- - "Method that tests the implementation logic of a CDS view entity - "Unlike the 'test_select_cds' method, this method does not call a method - "in the class under test. - - "Preparing test data - prepare_testdata_set_cds( ). - - "Retrieving data from the CDS view entity (test data is used here) - SELECT * FROM zdemo_abap_cds_ve_agg_exp INTO TABLE @DATA(result). - - "Verifying the result - cl_abap_unit_assert=>assert_equals( - act = result[ 1 ]-carrid - exp = 'XX' - msg = `The value does not match the expected result` - quit = if_abap_unit_constant=>quit-no ). - - cl_abap_unit_assert=>assert_equals( - act = result[ 1 ]-avg_seats_occ - exp = '411.75' - msg = `The value does not match the expected result` - quit = if_abap_unit_constant=>quit-no ). - - cl_abap_unit_assert=>assert_equals( - act = result[ 1 ]-max_occ_seats - exp = 458 - msg = `The value does not match the expected result` - quit = if_abap_unit_constant=>quit-no ). - ENDMETHOD. - -ENDCLASS. -``` - -**Class 2 `ZCL_DEMO_ABAP_UNIT_DATAPROV` (class representing a DOC)** - -Insert the following code in the `Global Class` tab - -```abap -CLASS zcl_demo_abap_unit_dataprov DEFINITION - PUBLIC - CREATE PUBLIC . - - PUBLIC SECTION. - -* -------------------------- NOTE ---------------------------------- -* This is an example class that represents a dependent-on-component -* (DOC). Methods of this class are called in another class. There, -* the DOCs are replaced by test doubles when running ABAP Unit tests. - - METHODS get_discount RETURNING VALUE(discount) TYPE decfloat34. - METHODS get_discount_value IMPORTING day_value TYPE i - time_value TYPE i - RETURNING VALUE(discount_value) TYPE decfloat34. - PROTECTED SECTION. - PRIVATE SECTION. -ENDCLASS. - - - -CLASS zcl_demo_abap_unit_dataprov IMPLEMENTATION. - METHOD get_discount. - "Getting the weekday - "1) Monday, 2) Tuesday, 3) Wednesday, 4) Thursday, 5) Friday, 6) Saturday, 7) Sunday - DATA(weekday) = ( 5 + CONV d( xco_cp=>sy->date( xco_cp_time=>time_zone->utc - )->as( xco_cp_time=>format->iso_8601_basic )->value ) MOD 7 ) MOD 7 + 1. - - "- Standard discount is granted at the weekend (Saturday, Sunday) - "- On other weekdays, discount is granted depending on the daytime - IF weekday = 6 OR weekday = 7. - discount = '20'. - ELSE. - "Retrieving the current time in UTC - DATA(utc_time) = CONV t( xco_cp=>sy->time( xco_cp_time=>time_zone->utc - )->as( xco_cp_time=>format->iso_8601_basic )->value ). - - discount = COND #( WHEN utc_time BETWEEN '000000' AND '045959' THEN '15' "Night discount - WHEN utc_time BETWEEN '220000' AND '235959' THEN '15' "Night discount - WHEN utc_time BETWEEN '050000' AND '115959' THEN '10' "Morning discount - WHEN utc_time BETWEEN '180000' AND '215959' THEN '5' "Evening discount - ELSE 0 "No discount - ). - ENDIF. - ENDMETHOD. - - METHOD get_discount_value. - CASE day_value. - "Standard discount is granted at the weekend (Saturday, Sunday) - WHEN 6 OR 7. - discount_value = '20'. - "On other weekdays, discount is granted depending on the daytime - WHEN OTHERS. - discount_value = SWITCH #( time_value - WHEN 1 THEN '15' "Night discount - WHEN 2 THEN '10' "Morning discount - WHEN 3 THEN '5' "Evening discount - ELSE '0' "No discount - ). - ENDCASE. - ENDMETHOD. - -ENDCLASS. -``` - - -
- -

⬆️ back to top

## Running and Evaluating ABAP Unit Tests There are many ways to run ABAP unit tests as described [here](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/4ec4c6c66e391014adc9fffe4e204223.html?locale=en-US). @@ -1684,15 +590,22 @@ For more information about evaluating ABAP unit test results, see [here](https:/

⬆️ back to top

-## Executable Example +## Executable Examples + +- [zcl_demo_abap_unit_test](./src/zcl_demo_abap_unit_test.clas.abap): + - Explores test classes and test/special methods, implementing and injecting test doubles (constructor injection, back door injection, test seams) + - The example class is supported by other repository objects such as the interface [zdemo_abap_get_data_itf](./src/zdemo_abap_get_data_itf.intf.abap). +- [zcl_demo_abap_unit_tdf](./src/zcl_demo_abap_unit_tdf.clas.abap): + - Explores creating test doubles using ABAP frameworks + - It also explores test classes that are contained in an external class rather than the class being tested (`"! @testing ...` syntax). + - The example class is supported by two additional classes: [zcl_demo_abap_unit_dataprov](./src/zcl_demo_abap_unit_dataprov.clas.abap) (includes dependent-on-components that are replaced by test doubles when running ABAP Unit tests) and [ztcl_demo_abap_unit_tdf_testcl](./src/ztcl_demo_abap_unit_tdf_testcl.clas.abap) (includes the test class for testing class `zcl_demo_abap_unit_tdf`; the local test class includes the `"! @testing ...` syntax). -[zcl_demo_abap_unit_test](./src/zcl_demo_abap_unit_test.clas.abap) > **💡 Note**
-> - The executable example ... -> - covers the following topics: -> - Test classes and test/special methods -> - Implementing and injecting test doubles (constructor injection, back door injection, test seams) -> - contains comments in the code for more information. +> - The executable examples contain comments in the code for more information. +> - The examples are designed to display output in the console when they are run using `F9`. However, the focus is on ABAP Unit tests. Choose `Ctrl/Cmd + Shift + F10` to run the unit tests. +> - The example classes are intentionally simplified and nonsemantic, designed to highlight basic unit tests and explore framework classes and methods. +> - They are not meant to serve as a model for proper unit test design. Always devise your own solutions for each unique case. +> - Refer to the code comments, SAP Help Portal and class documentation for additional information. > - The steps to import and run the code are outlined [here](README.md#-getting-started-with-the-examples). > - [Disclaimer](./README.md#%EF%B8%8F-disclaimer) \ No newline at end of file diff --git a/24_Builtin_Functions.md b/24_Builtin_Functions.md index d6131eb..64c08ab 100644 --- a/24_Builtin_Functions.md +++ b/24_Builtin_Functions.md @@ -16,6 +16,7 @@ - [More (Special) Functions](#more-special-functions) - [coalesce Function](#coalesce-function) - [More Information](#more-information) + - [Executable Examples](#executable-examples) This ABAP cheat sheet includes a variety of built-in functions in ABAP, along with code snippets to demonstrate their functionality. Many of the functions covered here are also included in other ABAP cheat sheets that focus on specific topics. @@ -537,7 +538,7 @@ Search functions

``` abap -DATA(str) =`Pieces of cakes.`. +DATA(str) = `Pieces of cakes.`. "---------------- find ---------------- "The find function searches for the substring specified and returns the offset @@ -1690,13 +1691,6 @@ SELECT SINGLE source_unit = unit`MI`, target_unit = unit`KM` ) AS miles_to_km, - "Converts Euro to US dollars using today's rate - currency_conversion( - amount = d34n`1`, - source_currency = char`EUR`, - target_currency = char`USD`, - exchange_rate_date = @( cl_abap_context_info=>get_system_date( ) ) ) AS eur_to_usd, - "Creating a unique UUID for each row uuid( ) AS uuid @@ -1755,4 +1749,13 @@ SELECT tab2~key_field, - [Built-in functions in ABAP](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenbuilt_in_functions.htm) - [Overview of functions](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenbuilt_in_functions_overview.htm) -- [Built-in functions that can be used by ABAP CDS and ABAP SQL](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenddic_builtin_functions.htm) \ No newline at end of file +- [Built-in functions that can be used by ABAP CDS and ABAP SQL](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenddic_builtin_functions.htm) + +## Executable Examples + +[zcl_demo_abap_builtin_func](./src/zcl_demo_abap_builtin_func.clas.abap) + + +> **💡 Note**
+> - The steps to import and run the code are outlined [here](README.md#-getting-started-with-the-examples). +> - [Disclaimer](./README.md#%EF%B8%8F-disclaimer) diff --git a/README.md b/README.md index 6acaf5d..01859c7 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ ABAP cheat sheets[^1] ... |[Working with XML and JSON in ABAP](21_XML_JSON.md)|Covers processing XML using class libraries, XML transformations using XSLT and Simple Transformations (ST), serializations (ABAP to XML) and deserializations (XML to ABAP), dealing with JSON data|[zcl_demo_abap_xml_json](./src/zcl_demo_abap_xml_json.clas.abap)| |[Released ABAP Classes](22_Released_ABAP_Classes.md)|Contains a selection of ABAP classes, serving as a quick introduction, along with code snippets to explore the functionality in action|- (The cheat sheet includes copy and paste code snippets and example classes)| |[Date, Time, and Time Stamp](23_Date_and_Time.md)|Covers how to handle and process dates, times, and time stamps in ABAP|[zcl_demo_abap_date_time](./src/zcl_demo_abap_date_time.clas.abap)| -|[Built-In Functions](24_Builtin_Functions.md)|Covers a variety of built-in functions in ABAP|- (The cheat sheet includes copy and paste code snippets)| +|[Built-In Functions](24_Builtin_Functions.md)|Covers a variety of built-in functions in ABAP|[zcl_demo_abap_builtin_func](./src/zcl_demo_abap_builtin_func.clas.abap)| |[Authorization Checks](25_Authorization_Checks.md)|Provides a high-level overview of explicit and implicit authorization checks in ABAP|- (The cheat sheet includes a copy and paste example class)| |[ABAP Dictionary](26_ABAP_Dictionary.md)|Covers a selection of repository objects in the ABAP Dictionary (DDIC) that represent global types|- (The cheat sheet includes a copy and paste example class)| |[Exceptions and Runtime Errors](27_Exceptions.md)|Provides an overview on exceptions and runtime errors|[zcl_demo_abap_error_handling](./src/zcl_demo_abap_error_handling.clas.abap)| diff --git a/src/zcl_demo_abap_builtin_func.clas.abap b/src/zcl_demo_abap_builtin_func.clas.abap new file mode 100644 index 0000000..dc4b7d8 --- /dev/null +++ b/src/zcl_demo_abap_builtin_func.clas.abap @@ -0,0 +1,1708 @@ +"!

Built-in Functions
ABAP cheat sheet example class

+"! +"!

The example class demonstrates built-in functions in ABAP.
+"! Choose F9 in ADT to run the class.

+"! +"!

Information

+"!

Find information on getting started with the example class and the disclaimer in +"! the ABAP Doc comment of class {@link zcl_demo_abap_aux}.

+CLASS zcl_demo_abap_builtin_func DEFINITION + PUBLIC + FINAL + CREATE PUBLIC . + + PUBLIC SECTION. + INTERFACES if_oo_adt_classrun. + CLASS-METHODS class_constructor. + PROTECTED SECTION. + PRIVATE SECTION. +ENDCLASS. + +CLASS zcl_demo_abap_builtin_func IMPLEMENTATION. + METHOD if_oo_adt_classrun~main. + + out->write( |ABAP cheat sheet example: Built-in Functions\n\n| ). + out->write( `1) Logical Functions` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& boolc +*&---------------------------------------------------------------------* + + "boolc returns an X or a blank of type string + DATA(int) = 0. + + DATA(boolc1) = CONV abap_bool( boolc( int IS INITIAL ) ). + out->write( data = boolc1 name = `boolc1` ). + out->write( |\n| ). + + DATA(boolc2) = |#{ boolc( int IS INITIAL ) }#|. + out->write( data = boolc2 name = `boolc2` ). + out->write( |\n| ). + + DATA(boolc3) = |#{ boolc( int IS NOT INITIAL ) }#|. + out->write( data = boolc3 name = `boolc3` ). + out->write( |\n| ). + + "Using the translate function to return a value other than X/blank + DATA(boolc4) = translate( val = boolc( int BETWEEN -3 AND 3 ) from = `X` to = `1` ). + out->write( data = boolc4 name = `boolc4` ). + out->write( |\n| ). + + DATA(boolc5) = translate( val = boolc( int <> 0 ) from = ` ` to = `0` ). + out->write( data = boolc5 name = `boolc5` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& xsdbool +*&---------------------------------------------------------------------* + + + DATA(xsdb1) = xsdbool( 3 > 1 ). + out->write( data = xsdb1 name = `xsdb1` ). + out->write( |\n| ). + + DATA(xsdb2) = |#{ xsdbool( 1 = 1 ) }#|. + out->write( data = xsdb2 name = `xsdb2` ). + out->write( |\n| ). + + DATA(xsdb3) = |#{ xsdbool( 1 <> 1 ) }#|. + out->write( data = xsdb3 name = `xsdb3` ). + out->write( |\n| ). + + "Comparison with boolc + IF boolc( 1 = 0 ) = xsdbool( 1 = 0 ). + DATA(res) = `equal`. + ELSE. + res = `not equal`. + ENDIF. + out->write( data = res name = `res` ). + out->write( |\n| ). + + "Using xsdbool instead of, for example, an IF control + "structure or an expression with the COND operator + DATA(xsdb4) = xsdbool( -1 < 1 ). + out->write( data = xsdb4 name = `xsdb4` ). + out->write( |\n| ). + + DATA truth_value1 TYPE abap_bool. + IF -1 < 1. + truth_value1 = abap_true. + ELSE. + truth_value1 = abap_false. + ENDIF. + out->write( data = truth_value1 name = `truth_value1` ). + out->write( |\n| ). + + DATA(truth_value2) = COND #( WHEN -1 < 1 THEN abap_true ELSE abap_false ). + out->write( data = truth_value2 name = `truth_value2` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& contains, contains_any_of, contains_any_not_of +*&---------------------------------------------------------------------* + + "-------------------- contains -------------------- + "Specifying the minimum mandatory parameters + "Unlike most of the following examples, this one uses an IF control structure to + "visualize the truth value. + DATA cont1 TYPE abap_bool. + + IF contains( val = `abdefghijklmn` sub = `ghi` ). + cont1 = abap_true. + ELSE. + cont1 = abap_false. + ENDIF. + out->write( data = cont1 name = `cont1` ). + out->write( |\n| ). + + "case (abap_true is the default) + + DATA(cont2) = xsdbool( contains( val = `ABCDE` start = `ab` case = abap_true ) ). + out->write( data = cont2 name = `cont2` ). + out->write( |\n| ). + + DATA(cont3) = xsdbool( contains( val = `ABCDE` start = `ab` case = abap_false ) ). + out->write( data = cont3 name = `cont3` ). + out->write( |\n| ). + + "end + DATA(cont4) = xsdbool( contains( val = `UVWXYZ` end = `xyz` case = abap_false ) ). + out->write( data = cont4 name = `cont4` ). + out->write( |\n| ). + + "start + DATA(cont5) = xsdbool( contains( val = `123` start = `2` ) ). + out->write( data = cont5 name = `cont5` ). + out->write( |\n| ). + + "off/len can also be specified individually + "Not specifying off means 0 by default + DATA(cont6) = xsdbool( contains( val = `##ab## ##cd##` sub = `cd` len = 5 ) ). + out->write( data = cont6 name = `cont6` ). + out->write( |\n| ). + + DATA(cont7) = xsdbool( contains( val = `##ab## ##cd##` sub = `cd` off = 7 len = 5 ) ). + out->write( data = cont7 name = `cont7` ). + out->write( |\n| ). + + "occ: False if there are more occurrences than specified for occ; i.e. in the following + "example, specifying the values 1, 2, 3 returns true + "abap_true is returned for the first 3 loop passes, abap_false for the fourth + DO 4 TIMES. + DATA(cont8) = xsdbool( contains( val = `ab#ab#ab#cd#ef#gh` sub = `ab` occ = sy-index ) ). + out->write( data = cont8 name = `cont8` ). + out->write( |\n| ). + ENDDO. + + "pcre + "In the example, a blank is searched. + DATA(cont9) = xsdbool( contains( val = `Hallo world` pcre = `\s` ) ). + out->write( data = cont9 name = `cont9` ). + out->write( |\n| ). + + "-------------------- contains_any_of -------------------- + DATA(cont10) = xsdbool( contains_any_of( val = `abcdefg` sub = `xyza` ) ). + out->write( data = cont10 name = `cont10` ). + out->write( |\n| ). + + DATA(cont11) = xsdbool( contains_any_of( val = `abcdefg` sub = `xyz` ) ). + out->write( data = cont11 name = `cont11` ). + out->write( |\n| ). + + DATA(hi) = `1hallo`. + DATA(abc) = `abcdefghijklmnopqrstuvwxyz`. + DATA(cont12) = xsdbool( contains_any_of( val = hi start = abc ) ). + out->write( data = cont12 name = `cont12` ). + out->write( |\n| ). + + DATA(cont13) = xsdbool( contains_any_of( val = hi end = abc ) ). + out->write( data = cont13 name = `cont13` ). + out->write( |\n| ). + + "-------------------- contains_any_not_of -------------------- + DATA(cont14) = xsdbool( contains_any_not_of( val = hi start = abc ) ). + out->write( data = cont14 name = `cont14` ). + out->write( |\n| ). + + DATA(cont15) = xsdbool( contains_any_not_of( val = hi end = abc ) ). + out->write( data = cont15 name = `cont15` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& matches +*&---------------------------------------------------------------------* + + "Checking validity of an email address + "abap_true + DATA(matches) = xsdbool( matches( val = `jon.doe@email.com` + pcre = `\w+(\.\w+)*@(\w+\.)+(\w{2,4})` ) ). + out->write( data = matches name = `matches` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& line_exists +*&---------------------------------------------------------------------* + + TYPES: BEGIN OF s, + comp1 TYPE i, + comp2 TYPE c LENGTH 3, + END OF s. + DATA itab TYPE TABLE OF s WITH EMPTY KEY. + itab = VALUE #( ( comp1 = 1 comp2 = 'aaa' ) ( comp1 = 2 comp2 = 'bbb' ) ( comp1 = 3 comp2 = 'ccc' ) ). + DATA(str_tab) = VALUE string_table( ( `abc` ) ( `def` ) ( `ghi` ) ). + + + DATA(line_exists1) = xsdbool( line_exists( itab[ 1 ] ) ). + out->write( data = line_exists1 name = `line_exists1` ). + out->write( |\n| ). + + + DATA(line_exists2) = xsdbool( line_exists( itab[ 4 ] ) ). + out->write( data = line_exists2 name = `line_exists2` ). + out->write( |\n| ). + + + DATA(line_exists3) = xsdbool( line_exists( itab[ comp1 = 2 ] ) ). + out->write( data = line_exists3 name = `line_exists3` ). + out->write( |\n| ). + + + DATA(line_exists4) = xsdbool( line_exists( str_tab[ 2 ] ) ). + out->write( data = line_exists4 name = `line_exists4` ). + out->write( |\n| ). + + + DATA(line_exists5) = xsdbool( line_exists( str_tab[ table_line = `xxx` ] ) ). + out->write( data = line_exists5 name = `line_exists5` ). + out->write( |\n| ). + +********************************************************************** + + out->write( zcl_demo_abap_aux=>heading( `2) Numeric Functions` ) ). + +*&---------------------------------------------------------------------* +*& abs, sign, ceil, floor, trunc, frac, ipow +*&---------------------------------------------------------------------* + + "----------- abs: Returning the absolute value ----------- + + DATA(abs1) = abs( CONV decfloat34( '-4.756' ) ). + out->write( data = abs1 name = `abs1` ). + out->write( |\n| ). + + DATA(abs2) = abs( -4 ). + out->write( data = abs2 name = `abs2` ). + out->write( |\n| ). + + "----------- sign: Evaluating the sign ----------- + DATA(sign1) = sign( -789 ). + out->write( data = sign1 name = `sign1` ). + out->write( |\n| ). + + DATA(sign2) = sign( 5 - 5 ). + out->write( data = sign2 name = `sign2` ). + out->write( |\n| ). + + DATA(sign3) = sign( -5 * -5 ). + out->write( data = sign3 name = `sign3` ). + out->write( |\n| ). + + "----- ceil: smallest integer not less than the value specified ----- + + DATA(ceil1) = ceil( CONV decfloat34( '4.999' ) ). + out->write( data = ceil1 name = `ceil1` ). + out->write( |\n| ). + + DATA(ceil2) = ceil( CONV decfloat34( '4.001' ) ). + out->write( data = ceil2 name = `ceil2` ). + out->write( |\n| ). + + DATA(ceil3) = ceil( CONV decfloat34( '-4.999' ) ). + out->write( data = ceil3 name = `ceil3` ). + out->write( |\n| ). + + DATA(ceil4) = ceil( CONV decfloat34( '-4.001' ) ). + out->write( data = ceil4 name = `ceil4` ). + out->write( |\n| ). + + "----- floor: largest integer not less than the value specified ----- + + DATA(floor1) = floor( CONV decfloat34( '4.999' ) ). + out->write( data = floor1 name = `floor1` ). + out->write( |\n| ). + + DATA(floor2) = floor( CONV decfloat34( '4.001' ) ). + out->write( data = floor2 name = `floor2` ). + out->write( |\n| ). + + DATA(floor3) = floor( CONV decfloat34( '-4.999' ) ). + out->write( data = floor3 name = `floor3` ). + out->write( |\n| ). + + DATA(floor4) = floor( CONV decfloat34( '-4.001' ) ). + out->write( data = floor4 name = `floor4` ). + out->write( |\n| ). + + "------------- trunc: integer part ------------- + + DATA(trunc1) = trunc( CONV decfloat34( '4.999' ) ). + out->write( data = trunc1 name = `trunc1` ). + out->write( |\n| ). + + DATA(trunc2) = trunc( CONV decfloat34( '4.001' ) ). + out->write( data = trunc2 name = `trunc2` ). + out->write( |\n| ). + + DATA(trunc3) = trunc( CONV decfloat34( '-4.999' ) ). + out->write( data = trunc3 name = `trunc3` ). + out->write( |\n| ). + + DATA(trunc4) = trunc( CONV decfloat34( '-4.001' ) ). + out->write( data = trunc4 name = `trunc4` ). + out->write( |\n| ). + + "------------- frac: decimal places ------------- + + DATA(frac1) = frac( CONV decfloat34( '4.999' ) ). + out->write( data = frac1 name = `frac1` ). + out->write( |\n| ). + + DATA(frac2) = frac( CONV decfloat34( '4.001' ) ). + out->write( data = frac2 name = `frac2` ). + out->write( |\n| ). + + DATA(frac3) = frac( CONV decfloat34( '-4.999' ) ). + out->write( data = frac3 name = `frac3` ). + out->write( |\n| ). + + DATA(frac4) = frac( CONV decfloat34( '-4.001' ) ). + out->write( data = frac4 name = `frac4` ). + out->write( |\n| ). + + "------------- ipow: Calculalting the power ------------- + + DATA(ipow1) = ipow( base = 2 exp = 3 ). + out->write( data = ipow1 name = `ipow1` ). + out->write( |\n| ). + + DATA(ipow2) = ipow( base = 10 exp = 0 ). + out->write( data = ipow2 name = `ipow2` ). + out->write( |\n| ). + + "Exception is raised + TRY. + DATA(ipow3) = ipow( base = 10 exp = 100 ). + CATCH cx_sy_arithmetic_overflow INTO DATA(error). + out->write( error->get_text( ) ). + ENDTRY. + + +*&---------------------------------------------------------------------* +*& nmin, nmax +*&---------------------------------------------------------------------* + + "A minimum of two, and a maximum of 9 arguments can be specified. + "Numeric data objects and numeric expressions are possible + + DATA(nmin) = nmin( val1 = CONV decfloat34( '1.34' ) + val2 = CONV decfloat34( '56.7' ) + val3 = CONV decfloat34( '890.123' ) + val4 = CONV decfloat34( '0.999' ) ). + out->write( data = nmin name = `nmin` ). + out->write( |\n| ). + + DATA(nmax) = nmax( val1 = CONV decfloat34( '1.34' ) + val2 = CONV decfloat34( '56.7' ) + val3 = CONV decfloat34( '890.123' ) + val4 = CONV decfloat34( '0.999' ) ). + out->write( data = nmax name = `nmax` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& acos, asin, atan, cos, sin, tan, cosh, sinh, tanh, exp, log, log10, sqrt +*&---------------------------------------------------------------------* + + "Calculating the square root + + DATA(sqrt1) = sqrt( CONV decfloat34( '9' ) ). + out->write( data = sqrt1 name = `sqrt1` ). + out->write( |\n| ). + + DATA(sqrt2) = sqrt( CONV decfloat34( '40.96' ) ). + out->write( data = sqrt2 name = `sqrt2` ). + out->write( |\n| ). + + "Calculating the logarithm to base 10 + + DATA(log10) = log10( CONV decfloat34( '1000' ) ). + out->write( data = log10 name = `log10` ). + out->write( |\n| ). + + DATA(sine) = sin( '30' ). + out->write( data = sine name = `sine` ). + out->write( |\n| ). + + DATA(cosine) = cos( '45' ). + out->write( data = cosine name = `cosine` ). + out->write( |\n| ). + + DATA(tangent) = tan( '90' ). + out->write( data = tangent name = `tangent` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& round, rescale +*&---------------------------------------------------------------------* + + "Rounding to decimal places + + DATA(round1) = round( val = CONV decfloat34( '1.2374' ) dec = 2 ). + out->write( data = round1 name = `round1` ). + out->write( |\n| ). + + DATA(round2) = round( val = CONV decfloat34( '1.2374' ) dec = 3 ). + out->write( data = round2 name = `round2` ). + out->write( |\n| ). + + "Rounding to precision + + DATA(round3) = round( val = CONV decfloat34( '1234567890123' ) prec = 10 ). + out->write( data = round3 name = `round3` ). + out->write( |\n| ). + + DATA(round4) = round( val = CONV decfloat34( '1234' ) prec = 3 ). + out->write( data = round4 name = `round4` ). + out->write( |\n| ). + + "Rescaling function + "Similar to the round function, the dec (for scaling) or prec (for precision) + "parameters must be specified. The input is rounded if required. + + DATA(rescale1) = rescale( val = CONV decfloat34( '1234.56789' ) dec = 0 ). + out->write( data = rescale1 name = `rescale1` ). + out->write( |\n| ). + + DATA(rescale2) = rescale( val = CONV decfloat34( '1234.56789' ) dec = 1 ). + out->write( data = rescale2 name = `rescale2` ). + out->write( |\n| ). + + DATA(rescale3) = rescale( val = CONV decfloat34( '1234.56789' ) prec = 3 ). + out->write( data = rescale3 name = `rescale3` ). + out->write( |\n| ). + + DATA(rescale4) = rescale( val = CONV decfloat34( '1234.56789' ) prec = 10 ). + out->write( data = rescale4 name = `rescale4` ). + out->write( |\n| ). + +********************************************************************** + + out->write( zcl_demo_abap_aux=>heading( `3) String Functions` ) ). + +*&---------------------------------------------------------------------* +*& numofchar, strlen, xstrlen +*&---------------------------------------------------------------------* + + "numofchar: Trailing blanks are not counted in both strings of fixed and variable length + "strlen: Trailing blanks are not counted in strings of fixed length; in strings of + " variable length, they are counted + + DATA(numofchar1) = numofchar( 'abc ' ). + out->write( data = numofchar1 name = `numofchar1` ). + out->write( |\n| ). + + DATA(numofchar2) = numofchar( `abc ` ). + out->write( data = numofchar2 name = `numofchar2` ). + out->write( |\n| ). + + + DATA(strlen1) = strlen( 'abc ' ). + out->write( data = strlen1 name = `strlen1` ). + out->write( |\n| ). + + + DATA(strlen2) = strlen( `abc ` ). + out->write( data = strlen2 name = `strlen2` ). + out->write( |\n| ). + + "xstrlen for type xstring + DATA(xstr) = CONV xstring( `480065006C006C006F00200077006F0072006C0064002100` ). + + DATA(len_xstr) = xstrlen( xstr ). + out->write( data = len_xstr name = `len_xstr` ). + out->write( |\n| ). + + "xstring -> string + DATA(conv_str) = cl_abap_conv_codepage=>create_in( )->convert( xstr ). + +*&---------------------------------------------------------------------* +*& cmin, cmax +*&---------------------------------------------------------------------* + + DATA(cmin) = cmin( val1 = `zzzzzzz` + val2 = `zzazzzzzzzz` "smallest argument + val3 = `zzzzabc` ). + + out->write( data = cmin name = `cmin` ). + out->write( |\n| ). + + DATA(cmax) = cmax( val1 = `abcdef` "biggest argument + val2 = `aaghij` + val3 = `aaaaklmn` + val4 = `aaaaaaopqrs` + val5 = `aaaaaaaaaatuvwxy` + val6 = `aaaaaaaaaaaaaz` ). + out->write( data = cmax name = `cmax` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& find, find_end, find_any_of, find_any_not_of +*&---------------------------------------------------------------------* + + DATA(str) = `Pieces of cakes.`. + + "---------------- find ---------------- + "The find function searches for the substring specified and returns the offset + + DATA(find1) = find( val = str sub = `of` ). + out->write( data = find1 name = `find1` ). + out->write( |\n| ). + + DATA(find2) = find( val = str sub = `x` ). + out->write( data = find2 name = `find2` ). + out->write( |\n| ). + + "case + DATA(find3) = find( val = str sub = `p` case = abap_false ). + out->write( data = find3 name = `find3` ). + out->write( |\n| ). + + "off/len + DATA(find4) = find( val = str sub = `ca` off = 4 len = 5 ). + out->write( data = find4 name = `find4` ). + out->write( |\n| ). + + DATA(find5) = find( val = str sub = `ca` off = 4 len = 10 ). + out->write( data = find5 name = `find5` ). + out->write( |\n| ). + + "occ + DATA(find6) = find( val = str sub = `es` occ = 1 ). + out->write( data = find6 name = `find6` ). + out->write( |\n| ). + + DATA(find7) = find( val = str sub = `es` occ = 2 ). + out->write( data = find7 name = `find7` ). + out->write( |\n| ). + + DATA(find8) = find( val = str sub = `es` occ = 3 ). + out->write( data = find8 name = `find8` ). + out->write( |\n| ). + + "pcre + DATA(find9) = find( val = str pcre = `\.` ). + out->write( data = find9 name = `find9` ). + out->write( |\n| ). + + "---------------- find_end ---------------- + "find_end returns the sum of the offset of the occurrence plus the length of the match + + DATA(find_end1) = find_end( val = str sub = `of` ). + out->write( data = find_end1 name = `find_end1` ). + out->write( |\n| ). + + DATA(find_end2) = find_end( val = str pcre = `\s` ). + out->write( data = find_end2 name = `find_end2` ). + out->write( |\n| ). + + "---------------- find_any_of ---------------- + "find_any_of returns the offset of the occurrence of any character contained + "in a substring. The search is always case-sensitive. + + DATA(find_any_of1) = find_any_of( val = str sub = `x523z4e` ). + out->write( data = find_any_of1 name = `find_any_of1` ). + out->write( |\n| ). + + DATA(find_any_of2) = find_any_of( val = str sub = `zwq85t` ). + out->write( data = find_any_of2 name = `find_any_of2` ). + out->write( |\n| ). + + "---------------- find_any_not_of ---------------- + "find_any_not_of is the negation of find_any_of + + DATA(find_any_not_of1) = find_any_not_of( val = str sub = `ieces` ). + out->write( data = find_any_not_of1 name = `find_any_not_of1` ). + out->write( |\n| ). + + DATA(find_any_not_of2) = find_any_not_of( val = str sub = `P` ). + out->write( data = find_any_not_of2 name = `find_any_not_of2` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& count, count_any_of, count_any_not_of +*&---------------------------------------------------------------------* + + DATA(st) = `Pieces of cakes.`. + + "---------------- count ---------------- + + DATA(count1) = count( val = st sub = `e` ). + out->write( data = count1 name = `count1` ). + out->write( |\n| ). + + DATA(count2) = count( val = st sub = `x` ). + out->write( data = count2 name = `count2` ). + out->write( |\n| ). + + "case (case-sensitive by default) + DATA(count3) = count( val = st sub = `p` case = abap_false ). + out->write( data = count3 name = `count3` ). + out->write( |\n| ). + + "off/len (off is 0 by default; len is the length of sting by default minus offset) + DATA(count4) = count( val = st sub = `es` off = 3 ). + out->write( data = count4 name = `count4` ). + out->write( |\n| ). + + DATA(count5) = count( val = st sub = `es` off = 9 ). + out->write( data = count5 name = `count5` ). + out->write( |\n| ). + + DATA(count6) = count( val = st sub = `es` off = 3 len = 12 ). + out->write( data = count6 name = `count6` ). + out->write( |\n| ). + + DATA(count7) = count( val = st sub = `es` len = 5 ). + out->write( data = count7 name = `count7` ). + out->write( |\n| ). + + "pcre + DATA(count8) = count( val = st pcre = `\s` ). + out->write( data = count8 name = `count8` ). + out->write( |\n| ). + + DATA(count9) = count( val = st pcre = `.` ). + out->write( data = count9 name = `count9` ). + out->write( |\n| ). + + "---------------- count_any_of ---------------- + + DATA(count_any_of1) = count_any_of( val = st sub = `x523z4e` ). + out->write( data = count_any_of1 name = `count_any_of1` ). + out->write( |\n| ). + + DATA(count_any_of2) = count_any_of( val = st sub = `eco` ). + out->write( data = count_any_of2 name = `count_any_of2` ). + out->write( |\n| ). + + "---------------- count_any_not_of ---------------- + + DATA(count_any_not_of1) = count_any_not_of( val = st sub = `fP` ). + out->write( data = count_any_not_of1 name = `count_any_not_of1` ). + out->write( |\n| ). + + DATA(count_any_not_of2) = count_any_not_of( val = st sub = `Piecs ofak.` ). + out->write( data = count_any_not_of2 name = `count_any_not_of2` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& distance +*&---------------------------------------------------------------------* + + DATA(str_to_check) = `abap`. + + DATA(dist1) = distance( val1 = str_to_check val2 = `abap` ). + out->write( data = dist1 name = `dist1` ). + out->write( |\n| ). + + DATA(dist2) = distance( val1 = str_to_check val2 = `axbap` ). + out->write( data = dist2 name = `dist2` ). + out->write( |\n| ). + + DATA(dist3) = distance( val1 = str_to_check val2 = `yabyyapy` ). + out->write( data = dist3 name = `dist3` ). + out->write( |\n| ). + + DATA(dist4) = distance( val1 = str_to_check val2 = `zabapzzzzzzzzzzzz` max = 5 ). + out->write( data = dist4 name = `dist4` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& repeat +*&---------------------------------------------------------------------* + + "abapabapabapabapabap + DATA(repeat1) = repeat( val = `abap` occ = 5 ). + out->write( data = repeat1 name = `repeat1` ). + out->write( |\n| ). + + DATA(repeat2) = |#{ repeat( val = ` ` occ = 10 ) }#|. + out->write( data = repeat2 name = `repeat2` ). + out->write( |\n| ). + + "Y (initial value returned) + DATA(repeat3) = COND #( WHEN repeat( val = `a` occ = 0 ) = `` THEN `Y` ELSE `Z` ). + out->write( data = repeat3 name = `repeat3` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& condense +*&---------------------------------------------------------------------* + + DATA(str_to_condense) = ` ab cd `. + + "No parameters specified, i. e. their default values are provided. + "Works like CONDENSE statement without the NO-GAPS addition. + DATA(condense1) = condense( str_to_condense ). + out->write( data = condense1 name = `condense1` ). + out->write( |\n| ). + + "Parameters del/to not specified. from parameter with initial string + "(could also be a text field literal: from = ' '). This way, leading and + "trailing blanks are removed. + DATA(condense2) = condense( val = str_to_condense from = `` ). + out->write( data = condense2 name = `condense2` ). + out->write( |\n| ). + + "Parameter to specified with an initial string. No other parameters. + "Works like the CONDENSE statement with the NO-GAPS addition. + DATA(condense3) = condense( val = str_to_condense to = `` ). + out->write( data = condense3 name = `condense3` ). + out->write( |\n| ). + + "Parameter del specifies the leading/trailing characters to be removed. + DATA(condense4) = condense( val = `##see###you##` del = `#` ). + out->write( data = condense4 name = `condense4` ). + out->write( |\n| ). + + "If from and to are specified along with del, leading/trailing characters + "specified in del are first removed. Then, in the remaining string, all + "substrings composed of characters specified in from are replaced with the + "first character of the string specified in the to parameter. + DATA(condense5) = condense( val = ` Rock'xxx'Roller` + del = `re ` + from = `x` + to = `n` ). + out->write( data = condense5 name = `condense5` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& concat_lines_of +*&---------------------------------------------------------------------* + + DATA(stringtable) = VALUE string_table( ( `a` ) ( `b` ) ( `c` ) ). + + DATA(con1) = concat_lines_of( table = stringtable ). + out->write( data = con1 name = `con1` ). + out->write( |\n| ). + + DATA(con2) = concat_lines_of( table = stringtable sep = ` ` ). + out->write( data = con2 name = `con2` ). + out->write( |\n| ). + + DATA(con3) = concat_lines_of( table = stringtable sep = `/` ). + out->write( data = con3 name = `con3` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& reverse +*&---------------------------------------------------------------------* + + DATA(reverse) = reverse( `paba` ). + out->write( data = reverse name = `reverse` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& escape +*&---------------------------------------------------------------------* + + "Context: URLs + DATA(esc1) = escape( val = '...test: 5@8...' + format = cl_abap_format=>e_url_full ). + out->write( data = esc1 name = `esc1` ). + out->write( |\n| ). + + "Context: JSON + DATA(esc2) = escape( val = 'some "test" json \ with backslash and double quotes' + format = cl_abap_format=>e_json_string ). + out->write( data = esc2 name = `esc2` ). + out->write( |\n| ). + + "Context: String templates + DATA(esc3) = escape( val = 'Special characters in string templates: |, \, {, }' + format = cl_abap_format=>e_string_tpl ). + out->write( data = esc3 name = `esc3` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& insert +*&---------------------------------------------------------------------* + + DATA(to_be_inserted) = `ABAP`. + + DATA(insert1) = insert( val = to_be_inserted sub = `#` ). + out->write( data = insert1 name = `insert1` ). + out->write( |\n| ). + + DATA(insert2) = insert( val = to_be_inserted sub = `#` off = 1 ). + out->write( data = insert2 name = `insert2` ). + out->write( |\n| ). + + DATA(insert3) = insert( val = to_be_inserted sub = `#` off = strlen( to_be_inserted ) ). + out->write( data = insert3 name = `insert3` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& match +*&---------------------------------------------------------------------* + + DATA(match1) = match( val = `The email address is jon.doe@email.com.` + pcre = `\w+(\.\w+)*@(\w+\.)+(\w{2,4})` ). + out->write( data = match1 name = `match1` ). + out->write( |\n| ). + + "Find blank (without inlcuding it in the result indicated by \K) and + "the following 2 characters, second occurrence + DATA(match2) = match( val = `The email address is jon.doe@email.com.` + pcre = `\s\K..` + occ = 2 ). + out->write( data = match2 name = `match2` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& replace +*&---------------------------------------------------------------------* + + DATA(to_be_replaced) = `Pieces of cakes.`. + + DATA(replace1) = replace( val = to_be_replaced sub = `es` with = `#` ). + out->write( data = replace1 name = `replace1` ). + out->write( |\n| ). + + "case + DATA(replace2) = replace( val = to_be_replaced sub = `p` case = abap_false with = `#` ). + out->write( data = replace2 name = `replace2` ). + out->write( |\n| ). + + "occ + DATA(replace3) = replace( val = to_be_replaced sub = ` ` occ = 2 with = `#` ). + out->write( data = replace3 name = `replace3` ). + out->write( |\n| ). + + "The value 0 in occ means respecting all occurrences. + DATA(replace4) = replace( val = to_be_replaced sub = `e` occ = 0 with = `#` ). + out->write( data = replace4 name = `replace4` ). + out->write( |\n| ). + + "pcre + DATA(replace5) = replace( val = to_be_replaced pcre = `\s` with = `#` ). + out->write( data = replace5 name = `replace5` ). + out->write( |\n| ). + + DATA(replace6) = replace( val = to_be_replaced pcre = `\s` occ = 2 with = `#` ). + out->write( data = replace6 name = `replace6` ). + out->write( |\n| ). + + "Replacement determined by offset/length specification only (no sub/pcre specification) + DATA(replace7) = replace( val = to_be_replaced off = 5 with = `#` ). + out->write( data = replace7 name = `replace7` ). + out->write( |\n| ). + + DATA(replace8) = replace( val = to_be_replaced len = 5 with = `#` ). + out->write( data = replace8 name = `replace8` ). + out->write( |\n| ). + + DATA(replace9) = replace( val = to_be_replaced off = 3 len = 7 with = `#` ). + out->write( data = replace9 name = `replace9` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& segment +*&---------------------------------------------------------------------* + + "index: Number of segment + "sep: Substring specified is searched and used as limit + DATA(segment1) = segment( val = `Hallo,world,123` index = 1 sep = `,` ). + out->write( data = segment1 name = `segment1` ). + out->write( |\n| ). + + DATA(segment2) = segment( val = `Hallo,world,123` index = -1 sep = `,` ). + out->write( data = segment2 name = `segment2` ). + out->write( |\n| ). + + DATA(segment3) = segment( val = `Hallo
world
123` index = 2 sep = `
` ). + out->write( data = segment3 name = `segment3` ). + out->write( |\n| ). + + "space: Each individual character is searched and used as limit + DATA(to_be_segmented) = `a/b#c d.e`. + + DATA(segment4) = segment( val = `a/b#c d.e` index = 2 space = `. #/` ). + out->write( data = segment4 name = `segment4` ). + out->write( |\n| ). + + DATA segment_tab TYPE string_table. + DO. + TRY. + INSERT segment( val = to_be_segmented + index = sy-index + space = `. #/` ) INTO TABLE segment_tab. + CATCH cx_sy_strg_par_val. + EXIT. + ENDTRY. + ENDDO. + out->write( data = segment_tab name = `segment_tab` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& shift_left, shift_right +*&---------------------------------------------------------------------* + + DATA(to_be_shifted) = ` hallo `. + + "------------------- shift_left ------------------- + DATA(shift_left1) = shift_left( val = to_be_shifted places = 3 ). + out->write( data = shift_left1 name = `shift_left1` ). + out->write( |\n| ). + + "circular parameter: characters that are moved out of the string are + "added at the other end again + DATA(shift_left2) = shift_left( val = to_be_shifted circular = 2 ). + out->write( data = shift_left2 name = `shift_left2` ). + out->write( |\n| ). + + DATA(shift_left3) = shift_left( val = to_be_shifted sub = ` hal` ). + out->write( data = shift_left3 name = `shift_left3` ). + out->write( |\n| ). + + "No parameter except val: Behaves as if sub was passed a blank character + DATA(shift_left4) = shift_left( val = to_be_shifted ). + out->write( data = shift_left4 name = `shift_left4` ). + out->write( |\n| ). + + DATA(shift_left5) = shift_left( val = to_be_shifted sub = ` ` ). + out->write( data = shift_left5 name = `shift_left5` ). + out->write( |\n| ). + + "------------------- shift_right ------------------- + + DATA(shift_right1) = shift_right( val = to_be_shifted places = 3 ). + out->write( data = shift_right1 name = `shift_right1` ). + out->write( |\n| ). + + DATA(shift_right2) = shift_right( val = to_be_shifted circular = 2 ). + out->write( data = shift_right2 name = `shift_right2` ). + out->write( |\n| ). + + DATA(shift_right3) = shift_right( val = to_be_shifted sub = `o ` ). + out->write( data = shift_right3 name = `shift_right3` ). + out->write( |\n| ). + + DATA(shift_right4) = shift_right( val = to_be_shifted ). + out->write( data = shift_right4 name = `shift_right4` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& substring, substring_after, substring_before, substring_to, substring_from +*&---------------------------------------------------------------------* + + DATA(s4func) = `Lorem ipsum dolor sit amet`. + + "------------------- substring ------------------- + "Extracting substring starting at a specific position + "'len' not specified means the rest of the remaining characters is + "respected + DATA(substr1) = substring( val = s4func off = 6 ). + out->write( data = substr1 name = `substr1` ). + out->write( |\n| ). + + "Extracting substring with a specific length + "'off' is not specified and has the default value 0. + DATA(substr2) = substring( val = s4func len = 5 ). + out->write( data = substr2 name = `substr2` ). + out->write( |\n| ). + + "Specifying both off and len parameters + DATA(substr3) = substring( val = s4func off = 6 len = 5 ). + out->write( data = substr3 name = `substr3` ). + out->write( |\n| ). + + "------------------- substring_after ------------------- + "Extracting a substring ... + "... after a specified substring + DATA(substr_after1) = substring_after( val = s4func sub = `or` ). + out->write( data = substr_after1 name = `substr_after1` ). + out->write( |\n| ). + + "... after a specified substring specifying the occurence in a string + "and restricting the length + "occ/case + DATA(substr_after2) = substring_after( val = s4func sub = `oR` occ = 2 len = 7 case = abap_false ). + out->write( data = substr_after2 name = `substr_after2` ). + out->write( |\n| ). + + "pcre + DATA(substr_after3) = substring_after( val = s4func pcre = `\s.` occ = 2 ). + out->write( data = substr_after3 name = `substr_after3` ). + out->write( |\n| ). + + "------------------- substring_before ------------------- + "... before a specified substring + DATA(substr_before) = substring_before( val = s4func sub = `um` ). + out->write( data = substr_before name = `substr_before` ). + out->write( |\n| ). + + "------------------- substring_from ------------------- + "... from a specified substring on. It includes the substring specified + "in sub. len/off and other parameters are possible. + DATA(substr_from) = substring_from( val = s4func sub = `um` ). + out->write( data = substr_from name = `substr_from` ). + out->write( |\n| ). + + "Compared to substring_after + DATA(substr_after4) = substring_after( val = s4func sub = `um` ). + out->write( data = substr_after4 name = `substr_after4` ). + out->write( |\n| ). + + "------------------- substring_to ------------------- + "... up to a specified substring. It includes the substring specified + "in sub. len/off and other parameters are possible. + DATA(substr_to) = substring_to( val = s4func sub = `um` ). + out->write( data = substr_to name = `substr_to` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& to_upper, to_lower, from_mixed, to_mixed +*&---------------------------------------------------------------------* + + "------------------- to_upper ------------------- + DATA(upper1) = to_upper( `AbaP` ). + out->write( data = upper1 name = `upper1` ). + out->write( |\n| ). + DATA(upper2) = to_upper( `abap` ). + out->write( data = upper2 name = `upper2` ). + out->write( |\n| ). + + "------------------- to_lower ------------------- + + DATA(lower1) = to_lower( `AbaP` ). + out->write( data = lower1 name = `lower1` ). + out->write( |\n| ). + DATA(lower2) = to_lower( `ABAP` ). + out->write( data = lower2 name = `lower2` ). + out->write( |\n| ). + + "------------------- from_mixed ------------------- + "sep: Inserts the first character specified in sep before each uppercase letter + "from left to right, starting with the second position + DATA(from_mixed1) = from_mixed( val = `ABAP` sep = `#` ). + out->write( data = from_mixed1 name = `from_mixed1` ). + out->write( |\n| ). + + "If 'case' is not specified or if the first character in the 'case' parameter is an + "uppercase letter, the entire string is transformed to uppercase, otherwise to + "lowercase. + DATA(from_mixed2) = from_mixed( val = `AbaP` sep = `#` ). + out->write( data = from_mixed2 name = `from_mixed2` ). + out->write( |\n| ). + + "Underscore is the default separator + DATA(from_mixed3) = from_mixed( val = `AbaP` ). + out->write( data = from_mixed3 name = `from_mixed3` ). + out->write( |\n| ). + + DATA(from_mixed4) = from_mixed( val = `AbaP` sep = `#` case = 'X' ). + out->write( data = from_mixed4 name = `from_mixed4` ). + out->write( |\n| ). + + DATA(from_mixed5) = from_mixed( val = `AbaP` sep = `#` case = 'x' ). + out->write( data = from_mixed5 name = `from_mixed5` ). + out->write( |\n| ). + + "min: Passing a positive number to specify a minimum number of characters + "that must appear before an uppercase letter from the start of the string + "or since the last insertion so that a separator is inserted. The default + "value for 'min' is 1. + DATA(from_mixed6) = from_mixed( val = `ABaP` sep = `#` min = 1 ). + out->write( data = from_mixed6 name = `from_mixed6` ). + out->write( |\n| ). + + DATA(from_mixed7) = from_mixed( val = `ABaaAaaaaAP` sep = `#` min = 3 ). + out->write( data = from_mixed7 name = `from_mixed7` ). + out->write( |\n| ). + + "------------------- to_mixed ------------------- + "Transforming all letters in the string to lowercase letters from the second + "position on. From left to right from the second position on, it removes + "occurrences of the first character specified in the 'sep' parameter from the + "string and transforms the next letter to an uppercase letter. + "Default separator _ + + DATA(to_mixed1) = to_mixed( val = `Abc_de_fg_hi` ). + out->write( data = to_mixed1 name = `to_mixed1` ). + out->write( |\n| ). + + DATA(to_mixed2) = to_mixed( val = `Abc/de/fg/hi` sep = `/` ). + out->write( data = to_mixed2 name = `to_mixed2` ). + out->write( |\n| ). + + "Specifying the case parameter + DATA(to_mixed3) = to_mixed( val = `AbcXdeXfgXhi` sep = `X` case = 'x' ). + out->write( data = to_mixed3 name = `to_mixed3` ). + out->write( |\n| ). + + "Specifying the min operator + DATA(to_mixed4) = to_mixed( val = `Abc/de/fg/hi` sep = `/` min = 2 ). + out->write( data = to_mixed4 name = `to_mixed4` ). + out->write( |\n| ). + + DATA(to_mixed5) = to_mixed( val = `Abc/de/fghijklmno/pq` sep = `/` min = 5 ). + out->write( data = to_mixed5 name = `to_mixed5` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& translate +*&---------------------------------------------------------------------* + + DATA(to_be_translated) = `___abc_def_____ghi_`. + + "Each character that occurs in the 'from' parameter is replaced by the character + "that occurs in the same place in the 'to' parameter as in the 'from' parameter. + "If 'to' is shorter than 'from', the surplus characters from 'from' are removed + "from the string. + DATA(translate1) = translate( val = to_be_translated from = `hi_` to = `#?` ). + out->write( data = translate1 name = `translate1` ). + out->write( |\n| ). + + DATA(translate2) = translate( val = to_be_translated from = `_` to = `#?` ). + out->write( data = translate2 name = `translate2` ). + out->write( |\n| ). + +********************************************************************** + + out->write( zcl_demo_abap_aux=>heading( `4) Time Stamp Functions` ) ). + + +*&---------------------------------------------------------------------* +*& utclong_current +*&---------------------------------------------------------------------* + + "The return value has the type utclong. + DATA(ts1) = utclong_current( ). + out->write( data = ts1 name = `ts1` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& utclong_add +*&---------------------------------------------------------------------* + + DATA(utc4calc) = CONV utclong( '2024-01-01 15:55:14.1173220' ). + + "At least one parameter must be specified besides 'val'. + "Note that there are no parameters for years and months. + + "Adding one hour + DATA(ts2) = utclong_add( val = utc4calc + hours = 1 ). + out->write( data = ts2 name = `ts2` ). + out->write( |\n| ). + + "Subtracting one hour by passing a negative integer value (no + "separate substract function is available) + DATA(ts3) = utclong_add( val = utc4calc + hours = -1 ). + out->write( data = ts3 name = `ts3` ). + out->write( |\n| ). + + "Using all parameters + DATA(ts4) = utclong_add( val = utc4calc + days = 1 + hours = 2 + minutes = CONV int8( '13' ) + seconds = CONV decfloat34( '53.12' ) ). + out->write( data = ts4 name = `ts4` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& utclong_diff +*&---------------------------------------------------------------------* + + DATA(ts5) = CONV utclong( '2024-01-01 05:30:00' ). + DATA(ts6) = CONV utclong( '2024-01-01 06:30:00' ). + + "The return value has the type decfloat34. It contains the exact difference in seconds. + + DATA(ts_diff1) = utclong_diff( high = ts6 + low = ts5 ). + out->write( data = ts_diff1 name = `ts_diff1` ). + out->write( |\n| ). + + + DATA(ts_diff2) = utclong_diff( high = ts5 + low = ts6 ). + out->write( data = ts_diff2 name = `ts_diff2` ). + out->write( |\n| ). + +********************************************************************** + + out->write( zcl_demo_abap_aux=>heading( `5) Table Functions` ) ). + + +*&---------------------------------------------------------------------* +*& lines +*&---------------------------------------------------------------------* + + DATA(strtab) = VALUE string_table( ( `aaa` ) ( `bbb` ) ( `ccc` ) ( `ddd` ) ( `eee` ) ). + + + DATA(lines1) = lines( strtab ). + out->write( data = lines1 name = `lines1` ). + out->write( |\n| ). + + DELETE strtab INDEX 1. + + DATA(lines2) = lines( strtab ). + out->write( data = lines2 name = `lines2` ). + out->write( |\n| ). + + CLEAR strtab. + + DATA(lines3) = lines( strtab ). + out->write( data = lines3 name = `lines3` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& line_index +*&---------------------------------------------------------------------* + + TYPES: BEGIN OF st, + comp1 TYPE i, + comp2 TYPE c LENGTH 3, + comp3 TYPE c LENGTH 3, + END OF st. + DATA itab_em TYPE TABLE OF st WITH EMPTY KEY. + DATA itab_sec TYPE HASHED TABLE OF st + WITH UNIQUE KEY comp1 + WITH NON-UNIQUE SORTED KEY sk COMPONENTS comp2. + + itab_em = VALUE #( ( comp1 = 1 comp2 = 'e' comp3 = 'z' ) + ( comp1 = 2 comp2 = 'd' comp3 = 'y' ) + ( comp1 = 3 comp2 = 'c' comp3 = 'x' ) + ( comp1 = 4 comp2 = 'b' comp3 = 'w' ) + ( comp1 = 5 comp2 = 'a' comp3 = 'v' ) ). + + itab_sec = itab_em. + DATA(itab_str) = VALUE string_table( ( `aaa` ) ( `bbb` ) ( `ccc` ) ( `ddd` ) ( `eee` ) ). + + "Note: + "The table expression must be determined by a key specification (explicit table + "key, free key). + + "Using a free key + + DATA(line_index1) = line_index( itab_em[ comp1 = 1 ] ). + out->write( data = line_index1 name = `line_index1` ). + out->write( |\n| ). + + + DATA(line_index2) = line_index( itab_em[ comp2 = 'd' ] ). + out->write( data = line_index2 name = `line_index2` ). + out->write( |\n| ). + + "Note: A hashed table does not have a primary table index. The result is -1. + DATA(line_index3) = line_index( itab_sec[ KEY primary_key comp1 = 1 ] ). + out->write( data = line_index3 name = `line_index3` ). + out->write( |\n| ). + + "Hashed tables can be assigned a secondary table index using a secondary + "table key. + + DATA(line_index4) = line_index( itab_sec[ KEY sk comp2 = 'd' ] ). + out->write( data = line_index4 name = `line_index4` ). + out->write( |\n| ). + + + DATA(line_index5) = line_index( itab_sec[ KEY sk comp2 = 'a' ] ). + out->write( data = line_index5 name = `line_index5` ). + out->write( |\n| ). + + "Specifying the pseudo component table_line + DATA(line_index6) = line_index( itab_str[ table_line = `aaa` ] ). + out->write( data = line_index6 name = `line_index6` ). + out->write( |\n| ). + + + DATA(line_index7) = line_index( itab_str[ table_line = `zzz` ] ). + out->write( data = line_index7 name = `line_index7` ). + out->write( |\n| ). + +********************************************************************** + + out->write( zcl_demo_abap_aux=>heading( `6) Built-In Functions for ABAP SQL` ) ). + +*&---------------------------------------------------------------------* +*& Functions for Numeric Values +*&---------------------------------------------------------------------* + + SELECT SINGLE + "Division, result rounded to an integer + + div( 4, 2 ) AS div, + + "Division, 3rd argument: result is rounded to the specified + "number of decimals + + division( 1, 3, 2 ) AS division, + + "Result is rounded to first greater integer + + ceil( decfloat34`1.333` ) AS ceil, + + "Result is the remainder of division + + mod( 3, 2 ) AS mod, + + "Largest integer value not greater than the specified value + + floor( decfloat34`1.333` ) AS floor, + + "Returns the absolute number + + abs( int4`-2` ) AS abs, + + "Result is rounded to the specified position after the decimal separator + + round( decfloat34`1.337`, 2 ) AS round + + FROM zdemo_abap_carr + WHERE carrid = 'LH' + INTO @DATA(numeric_functions). + + out->write( data = numeric_functions name = `numeric_functions` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& Functions for Strings +*&---------------------------------------------------------------------* + + SELECT SINGLE + carrid, "LH + carrname, "Lufthansa + url, "http://www.lufthansa.com + + "Concatenates strings, ignores trailing blanks + "LHLufthansa + concat( carrid, carrname ) AS concat, + + "Concatenates strings, number denotes the blanks that are inserted + "LH Lufthansa + concat_with_space( carrid, carrname, 1 ) AS concat_with_space, + + "First letter of a word -> uppercase, all other letters -> lowercase; + "note that a space and other special characters means a new word. + "Http://Www.Lufthansa.Com + initcap( url ) AS initcap, + + "Position of the first occurrence of the substring specified + + instr( carrname,'a' ) AS instr, + + "String of length n starting from the left of an expression; + "trailing blanks are ignored + "Luft + left( carrname, 4 ) AS left, + + "Number of characters in an expression, trailing blanks are ignored + + length( url ) AS length, + + "Checks if expression contains a PCRE expression; + "case-sensitive by default (case_sensitive parameter can be specified) + "Notes on the 1 = found, 0 = not found + + like_regexpr( pcre = '\..', "Period that is followed by any character + value = url ) AS like_regex, + + "Returns position of a substring in an expression, + + + + locate( carrname, 'a', 0, 2 ) AS locate, + + "Searches a PCRE pattern, returns offset of match; + "many optional parameters: occurrence, case_sensitive, start, group + + locate_regexpr( pcre = '\..', "Period followed by any character + value = url, + occurrence = 2 ) + AS locate_regexpr, + + "Searches a PCRE pattern, returns offset of match + 1; + "many optional parameters: occurrence, case_sensitive, start, group + + locate_regexpr_after( pcre = '.', "Any character + value = url, + occurrence = 1 ) AS locate_regexpr_after, + + "Removes leading characters as specified in the 2nd argument, + "trailing blanks are removed + "ufthansa + ltrim( carrname, 'L' ) AS ltrim, + + "Counts all occurrences of found PCRE patterns + + occurrences_regexpr( pcre = '\..', "Period that is followed by any character + value = url ) AS occ_regex, + + "Replaces the 2nd argument with the 3rd in an expression + "Lufth#ns# + replace( carrname, 'a', '#' ) AS replace, + + "Replaces a found PCRE expression; + "more parameters possible: occurrence, case_sensitive, start + "http://www#ufthansa#om + replace_regexpr( pcre = '\..', "Period that is followed by any character + value = url, + with = '#' ) AS replace_regex, + + "Extracts a string with the length specified starting from the right + "hansa + right( carrname, 5 ) AS right, + + "Expands string to length n (2nd argument); trailing blanks produced + "are replaced by the characters from the (3rd) argument + "Note that if n is less than the string, the expression is truncated + "on the right. + "Lufthansa### + rpad( carrname, 12, '#' ) AS rpad, + + "All trailing characters that match the character of the 2nd argument + "are removed; trailing blanks are removed, too + "Lufthans + rtrim( carrname, 'a' ) AS rtrim, + + "Returns a substring; 2nd argument = position from where to start; + + "fth + substring( carrname, 3, 3 ) AS substring, + + "Searches for a PCRE expression and returns the matched substring + "More parameters possible: occurrence, case_sensitive, start, group + ".lu + substring_regexpr( pcre = '\...', "Period that is followed by any two characters + value = url ) AS substring_regexpr, + + "All lower case letters are transformed to upper case letters + "LUFTHANSA + upper( carrname ) AS upper + + FROM zdemo_abap_carr + WHERE carrid = 'LH' + INTO @DATA(string_functions). + + out->write( data = string_functions name = `string_functions` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& Functions for Date, Time, and Time Stamps +*&---------------------------------------------------------------------* + + DATA da TYPE d VALUE '20240122'. + DATA ti TYPE t VALUE '123456'. + DATA utc TYPE utclong VALUE '2024-02-15 05:30:00'. + DATA tmst TYPE timestamp VALUE '20240808112458'. + DATA tmstlong TYPE timestampl VALUE '20240101081317.81011'. + + SELECT SINGLE FROM i_timezone + FIELDS + "---------------------- Date ---------------------- + "Generic date functions (types d, utclong) + "type t also possible; 1 + is_valid( @ti ) AS isvalid, + "In the following examples in this 'section', d and utclong are possible. + + extract_year( @utc ) AS extr_year, + + + extract_month( @da ) AS extr_month, + + + extract_day( @utc ) AS extr_day, + + "Monday + dayname( @da ) AS day_name, + + "February + monthname( @utc ) AS month_name, + + + weekday( @utc ) AS week_day, + + + days_between( @utc,utclong`2024-02-25 08:14:26` ) AS days_bw, + + + add_days( @da,2 ) AS add_days, + + + add_months( @utc,3 ) AS add_months, + + "Functions for the type datn + + datn_days_between( datn`20240111`,datn`20240212` ) AS days_datn_bw, + + + datn_add_days( datn`20240111`,4 ) AS days_datn_add, + + + datn_add_months( datn`20240111`,5 ) AS months_datn_add, + + "Functions for the type dats + + dats_is_valid( dats`20240812` ) AS dats_valid, + + + dats_days_between( dats`20240812`,dats`20240817` ) AS days_dats_bw, + + + dats_add_days( dats`20240812`,4 ) AS days_dats_add, + + + dats_add_months( dats`20240812`,3 ) AS months_dats_add, + + "---------------------- Time ---------------------- + "Generic time functions (types t and utclong) + "As above, types d and utclong also possible; 1 + is_valid( @ti ) AS time_is_valid, + + + extract_hour( @utc ) AS extr_hour, + + + extract_minute( @ti ) AS extr_min, + + + extract_second( @utc ) AS extr_sec, + + "Function for the type tims + + tims_is_valid( tims`231256` ) AS tims_is_valid, + + "---------------------- Time Stamp ---------------------- + "Note: The type utclong can be used in the generic functions above. + "Functions specific to the type utclong + "Generates a UTC time stamp; e.g. 2024-01-01 12:58:58.5070000 + utcl_current( ) AS utcl_current, + + + utcl_add_seconds( @utc,5 ) AS sec_add_utc, + + + utcl_seconds_between( utclong`2024-02-25 08:14:26`,utclong`2024-02-25 08:15:17` ) AS sec_bw_utc, + + "Functions specific to the type timetamp + + tstmp_is_valid( @tmst ) AS ts_is_valid, + + + tstmp_current_utctimestamp( ) AS ts_current, + + "The following two functions have an optional parameter on_error. + "Check the ABAP Keyword Documentation + + tstmp_seconds_between( tstmp1 = @tmst, + tstmp2 = CAST( dec`20240808112517` AS DEC( 15,0 ) ) ) AS sec_bw_ts, + + + tstmp_add_seconds( tstmp = @tmst, + seconds = CAST( dec`10` AS DEC( 15,0 ) ) ) AS sec_add_ts, + + "---------------------- Functions for conversions ---------------------- + "Note: For the following functions, optional parameters are possible. + "For more details, check the ABAP Keyword Documentation. + + tstmp_to_dats( tstmp = @tmst, + tzone = CAST( char`EST` AS CHAR( 6 ) ) ) AS tstmp_to_dats, + + + tstmp_to_tims( tstmp = @tmst, + tzone = CAST( char`EST` AS CHAR( 6 ) ) ) AS tstmp_to_tims, + + "X + tstmp_to_dst( tstmp = @tmst, + tzone = CAST( char`EST` AS CHAR( 6 ) ) ) AS tstmp_to_dst, + + + dats_tims_to_tstmp( date = @da, + time = @ti, + tzone = CAST( char`EST` AS CHAR( 6 ) ) ) AS dats_tims_to_tstmp, + + + tstmpl_to_utcl( tstmpl = @tmstlong ) AS tstmpl_to_utcl, + + + tstmpl_from_utcl( utcl = @utc ) AS tstmpl_from_utcl, + + + dats_to_datn( dats = dats`20240812` ) AS dats_to_datn, + + + dats_from_datn( datn = datn`20240111` ) AS dats_from_datn, + + + tims_to_timn( tims = tims`231256` ) AS tims_to_timn, + + + tims_from_timn( timn = timn`155432` ) AS tims_from_timn + + WHERE TimeZoneID = char`EST` + INTO @DATA(time_and_date_functions). + + out->write( data = time_and_date_functions name = `time_and_date_functions` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& More (Special) Functions +*&---------------------------------------------------------------------* + + SELECT SINGLE + carrid, + + "Type conversion: string of fixed length (e.g. of type c) to variable + "length string of type string + to_clob( carrid ) AS clob, + + "Byte string -> character string + bintohex( raw`1234` ) AS bintohex, + + "Character string -> byte string + hextobin( char`1234` ) AS hextobin, + + "Byte field of type RAW to a byte string (BLOB) of type RAWSTRING + to_blob( raw`1234` ) AS blob, + + "Unit and currency conversion functions + "More parameters are available. + + "Converts miles to kilometers + unit_conversion( quantity = d34n`1`, + source_unit = unit`MI`, + target_unit = unit`KM` ) AS miles_to_km, + + "Creating a unique UUID for each row + uuid( ) AS uuid + + FROM zdemo_abap_carr + WHERE carrid = char`LH` + INTO @DATA(special_functions). + + out->write( data = special_functions name = `special_functions` ). + out->write( |\n| ). + +*&---------------------------------------------------------------------* +*& coalesce Function +*&---------------------------------------------------------------------* + + "The null value is a special value that is returned by a database. It indicates an + "undefined value or result. Note that, in ABAP, there are no special null values. Do + "not confuse the null value with a type-dependent initial value. When using SELECT + "statements to read data, null values can be produced by, for example, outer joins. + "When the null values are passed to a data object, they are transformed to the + "type-dependent initial values. For more information, refer to the ABAP Keyword Documentation. + "The following example uses a left outer join to intentionally create null values. For + "this purpose, two demo database tables of the ABAP cheat sheet repository are cleared and + "populated with specific values to visualize null values. + DELETE FROM zdemo_abap_tab1. + DELETE FROM zdemo_abap_tab2. + MODIFY zdemo_abap_tab1 FROM TABLE @( VALUE #( ( key_field = 1 char1 = 'a' char2 = 'y' ) + ( key_field = 2 char1 = 'b' char2 = 'z' ) ) ). + MODIFY zdemo_abap_tab2 FROM TABLE @( VALUE #( ( key_field = 1 char1 = 'a' ) + ( key_field = 2 char1 = 'a' ) + ( key_field = 3 char1 = 'b' ) + ( key_field = 4 ) ) ). + + "Note that for the entry 'key_field = 4' no char1 value was passed. + "char1 is a shared column of the two database tables, and which is used in + "the ON condition of the join. Since there is no entry in char1 for 'key_field = 4', + "the joined values are null in that case. + "The coalesce function is used to replace null values produced by an outer join with + "a different value. + SELECT tab2~key_field, + coalesce( tab1~char1, '-' ) AS coalesced1, + coalesce( tab1~char2, '#' ) AS coalesced2 + FROM zdemo_abap_tab2 AS tab2 + LEFT OUTER JOIN zdemo_abap_tab1 AS tab1 ON tab1~char1 = tab2~char1 + INTO TABLE @DATA(join_w_null). + + out->write( data = join_w_null name = `join_w_null` ). + out->write( |\n| ). + + + ENDMETHOD. + + METHOD class_constructor. + "Filling demo database tables. + zcl_demo_abap_aux=>fill_dbtabs( ). + ENDMETHOD. + +ENDCLASS. diff --git a/src/zcl_demo_abap_builtin_func.clas.xml b/src/zcl_demo_abap_builtin_func.clas.xml new file mode 100644 index 0000000..1b949b0 --- /dev/null +++ b/src/zcl_demo_abap_builtin_func.clas.xml @@ -0,0 +1,16 @@ + + + + + + ZCL_DEMO_ABAP_BUILTIN_FUNC + E + ABAP cheat sheet: Built-in Functions + 1 + X + X + X + + + + diff --git a/src/zcl_demo_abap_unit_dataprov.clas.abap b/src/zcl_demo_abap_unit_dataprov.clas.abap new file mode 100644 index 0000000..612da24 --- /dev/null +++ b/src/zcl_demo_abap_unit_dataprov.clas.abap @@ -0,0 +1,67 @@ +"!

Class Supporting ABAP Unit Test Example
ABAP cheat sheet example class

+"! +"!

The example class represents a dependent-on-component (DOC) and supports an ABAP Unit test example. +"! Methods of this class are called in another class: {@link zcl_demo_abap_unit_tdf}. The DOCs are replaced +"! by test doubles when running ABAP Unit tests.
+"! Choose F9 in ADT to run the class.

+"! +"!

Information

+"!

Find information on getting started with the example class and the disclaimer in +"! the ABAP Doc comment of class {@link zcl_demo_abap_aux}.

+CLASS zcl_demo_abap_unit_dataprov DEFINITION + PUBLIC + CREATE PUBLIC . + + PUBLIC SECTION. + METHODS get_discount RETURNING VALUE(discount) TYPE decfloat34. + METHODS get_discount_value IMPORTING day_value TYPE i + time_value TYPE i + RETURNING VALUE(discount_value) TYPE decfloat34. + PROTECTED SECTION. + PRIVATE SECTION. +ENDCLASS. + + + +CLASS zcl_demo_abap_unit_dataprov IMPLEMENTATION. + METHOD get_discount. + "Getting the weekday + "1) Monday, 2) Tuesday, 3) Wednesday, 4) Thursday, 5) Friday, 6) Saturday, 7) Sunday + DATA(weekday) = ( 5 + CONV d( xco_cp=>sy->date( xco_cp_time=>time_zone->utc + )->as( xco_cp_time=>format->iso_8601_basic )->value ) MOD 7 ) MOD 7 + 1. + + "- Standard discount is granted at the weekend (Saturday, Sunday) + "- On other weekdays, discount is granted depending on the daytime + IF weekday = 6 OR weekday = 7. + discount = '20'. + ELSE. + "Retrieving the current time in UTC + DATA(utc_time) = CONV t( xco_cp=>sy->time( xco_cp_time=>time_zone->utc + )->as( xco_cp_time=>format->iso_8601_basic )->value ). + + discount = COND #( WHEN utc_time BETWEEN '000000' AND '045959' THEN '15' "Night discount + WHEN utc_time BETWEEN '220000' AND '235959' THEN '15' "Night discount + WHEN utc_time BETWEEN '050000' AND '115959' THEN '10' "Morning discount + WHEN utc_time BETWEEN '180000' AND '215959' THEN '5' "Evening discount + ELSE 0 "No discount + ). + ENDIF. + ENDMETHOD. + + METHOD get_discount_value. + CASE day_value. + "Standard discount is granted at the weekend (Saturday, Sunday) + WHEN 6 OR 7. + discount_value = '20'. + "On other weekdays, discount is granted depending on the daytime + WHEN OTHERS. + discount_value = SWITCH #( time_value + WHEN 1 THEN '15' "Night discount + WHEN 2 THEN '10' "Morning discount + WHEN 3 THEN '5' "Evening discount + ELSE '0' "No discount + ). + ENDCASE. + ENDMETHOD. + +ENDCLASS. diff --git a/src/zcl_demo_abap_unit_dataprov.clas.xml b/src/zcl_demo_abap_unit_dataprov.clas.xml new file mode 100644 index 0000000..4fc439b --- /dev/null +++ b/src/zcl_demo_abap_unit_dataprov.clas.xml @@ -0,0 +1,16 @@ + + + + + + ZCL_DEMO_ABAP_UNIT_DATAPROV + E + ABAP cheat sheet: Built-in Functions + 1 + X + X + X + + + + diff --git a/src/zcl_demo_abap_unit_tdf.clas.abap b/src/zcl_demo_abap_unit_tdf.clas.abap new file mode 100644 index 0000000..5642447 --- /dev/null +++ b/src/zcl_demo_abap_unit_tdf.clas.abap @@ -0,0 +1,458 @@ +"!

Creating Test Doubles Using ABAP Frameworks
ABAP cheat sheet example class

+"! +"!

The example class demonstrates managing dependencies (dependent-on-components, DOC) with ABAP Unit and +"! explores the creation of test doubles using ABAP frameworks. Additionally, the example explores test classes +"! not contained in the class to be tested, but rather in an external class.
+"! Choose F9 in ADT to run the class. To run all unit tests of the class, choose Ctrl/Cmd + Shift + F10.
+"! If the unit tests have not yet run, right-click the Foreign Tests entry in the ABAP Unit tab of ADT, +"! and choose Run.

+"! +"!

Topics covered

+"! +"! +"!

Running ABAP Unit tests

+"!
  1. Open the class with the ABAP development tools for Eclipse (ADT).
  2. +"!
  3. Choose Ctrl/Cmd + Shift + F10 to launch all tests. +"! You can also right-click somewhere in the class and choose Run as -> ABAP Unit Test. +"!
  4. As the test class is outside of this class in class {@link zcl_demo_abap_unit_tdf}, +"! the ABAP Unit tab will show a Foreign Tests entry. If the unit tests have not run, +"! right click, the Foreign Tests entry, and choose Run.
  5. +"!
  6. The results of a test run are displayed in the ABAP Unit tab in ADT +"! and can be evaluated. The Failure Trace section provides information +"! on errors found.
  7. +"!
  8. If you are interested in test coverage, you can choose Ctrl/Cmd + Shift + F11, +"! or make a right-click, choose Run as -> ABAP Unit Test With..., select the Coverage +"! checkbox and choose Execute. You can then check the results in the ABAP Coverage tab, +"! what code is tested and what not.
  9. +"!
+"! +"!

Information

+"!

Find information on getting started with the example class and the disclaimer in +"! the ABAP Doc comment of class {@link zcl_demo_abap_aux}.

+CLASS zcl_demo_abap_unit_tdf DEFINITION + PUBLIC + FINAL + CREATE PUBLIC . + + PUBLIC SECTION. + INTERFACES if_oo_adt_classrun. + "--------------- 1) ABAP OO Test Double Framework --------------- + "The examples in the test include use the cl_abap_testdouble class. + + "----------- 1a) Demonstrating constructor injection ----------- + + "Specifying the instance constructor with an optional parameter + "for the purpose of constructor injection as injection mechanism. + "In this example, methods are called from another, non-final class. + "They represent DOCs. Test doubles are injected when running unit + "tests. The parameter expects an instance of this class. + METHODS constructor + IMPORTING oref_constr_inj TYPE REF TO zcl_demo_abap_unit_dataprov OPTIONAL. + + "Declaring an object reference variable that will be used to call + "methods of an external class (the DOC) + DATA oref_data_provider TYPE REF TO zcl_demo_abap_unit_dataprov. + + "Method that is tested and contains a DOC. In this case, it is an + "external method that is called in the implementation part. The DOC + "is replaced by a test double created using cl_abap_testdouble. + METHODS td_constr_inj_calc_discount + IMPORTING + value TYPE numeric + RETURNING + VALUE(result) TYPE decfloat34. + + "----------- 1b) Demonstrating parameter injection ----------- + + "Method that is tested and contains a DOC. In this case, it is an + "external method that is called in the implementation part. The DOC + "is replaced by a test double created using cl_abap_testdouble. + "The example demonstrates parameter injection. Therefore, an + "optional importing parameter is included. When running the unit + "test, 'data_prov' is bound, and the test double is injected. + METHODS td_param_inj_calc_discount + IMPORTING value TYPE numeric + date TYPE d + time TYPE t + data_prov TYPE REF TO zcl_demo_abap_unit_dataprov OPTIONAL + EXPORTING message TYPE string + weekday TYPE string + RETURNING VALUE(result) TYPE decfloat34. + + "----------- 2) ABAP SQL Test Double Framework ----------- + "The examples in the test include use the cl_osql_test_environment + "class. Here, a database table represents the DOC. + "The method includes a SELECT statement. + + METHODS sql_get_shortest_flight_time IMPORTING carrier TYPE zdemo_abap_flsch-carrid + RETURNING VALUE(shortest_flight) TYPE i. + + "----------- 3) ABAP CDS Test Double Framework ----------- + "The examples in the test include use the cl_cds_get_data_set_environment class. + "Here, a CDS view entity represents the DOC. The method includes a SELECT + "statement. Another test method is implemented in the test inlcude that + "demonstrates the testing of a CDS view entity (without testing a method in the + "class under test). + METHODS cds_get_data_set IMPORTING carrier TYPE zdemo_abap_cds_ve_agg_exp-carrid + RETURNING VALUE(agg_line) TYPE zdemo_abap_cds_ve_agg_exp. + + "----------- 4) Managing dependencies on RAP business objects ----------- + "----------- 4a) Demonstrating mocking ABAP EML APIs -------------------- + "----------- ABAP EML read operations ----------------------------------- + "The examples in the test include use the cl_botd_mockemlapi_bo_test_env class. + + "Populating database tables for ABAP EML read requests + METHODS prep_dbtab_for_eml IMPORTING read_op TYPE abap_boolean DEFAULT abap_false. + TYPES read_tab_ro TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m. + TYPES read_tab_ch TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m\_child. + + "Method that includes an ABAP EML read request on the root entity + METHODS eml_read_root IMPORTING key TYPE zdemo_abap_rap_ro_m-key_field + RETURNING VALUE(tab_ro) TYPE read_tab_ro. + + "Method that includes an ABAP EML read-by-associaton request + METHODS eml_rba IMPORTING key TYPE zdemo_abap_rap_ro_m-key_field + RETURNING VALUE(tab_ch) TYPE read_tab_ch. + + "----------- ABAP EML modify operation ----------- + TYPES modify_tab_ro TYPE TABLE FOR CREATE zdemo_abap_rap_ro_m. + TYPES ty_mapped TYPE RESPONSE FOR MAPPED EARLY zdemo_abap_rap_ro_m. + TYPES ty_failed TYPE RESPONSE FOR FAILED EARLY zdemo_abap_rap_ro_m. + TYPES ty_reported TYPE RESPONSE FOR REPORTED EARLY zdemo_abap_rap_ro_m. + + "Method that includes an ABAP EML modify request on the root entity + METHODS eml_modify_root IMPORTING VALUE(instances) TYPE modify_tab_ro + EXPORTING mapped TYPE ty_mapped + failed TYPE ty_failed + reported TYPE ty_reported. + + "----------- 4b) Demonstrating transactional buffer test doubles --------- + "The examples in the test include use the cl_botd_txbufdbl_bo_test_env class. + TYPES read_tab_ro_u TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_u. + + "Method that includes an ABAP EML read request on the root entity + METHODS eml_read_root_buffer_td IMPORTING key TYPE zdemo_abap_rap_ro_u-key_field + RETURNING VALUE(tab_ro_u) TYPE read_tab_ro_u. + PROTECTED SECTION. + PRIVATE SECTION. +ENDCLASS. + +CLASS zcl_demo_abap_unit_tdf IMPLEMENTATION. + METHOD if_oo_adt_classrun~main. + + out->write( |ABAP Cheat Sheet Example: Creating Test Doubles Using ABAP Frameworks\n| ). + + out->write( `*********************************************************************` ). + out->write( |* *| ). + out->write( `* ---> Choose Ctrl/Cmd + Shift + F10 to launch all unit tests <--- *` ). + out->write( |* *| ). + out->write( `* As the example is set up with a test class outside of this class *` ). + out->write( `* and if the unit tests have not yet run, right-click the Foreign *` ). + out->write( `* Tests entry in the ABAP Unit tab of ADT, and choose Run. *` ). + out->write( |* *| ). + out->write( |*********************************************************************\n\n| ). + + "This implementation includes method calls of those methods that are unit tested + "in the test include. + "The implementation is just for demonstration purposes to explore the effect of + "the methods. + + "Populating demo database tables (only required for running the example class + "with F9 and exploring the effect of various method calls) + zcl_demo_abap_aux=>fill_dbtabs( ). + + "----- 1) Methods demonstrating the ABAP OO Test Double Framework in the test include ----- + "----- 1a) Using constructor injection ----- + DATA(td_constr_inj_calc_discount) = td_constr_inj_calc_discount( 100 ). + out->write( data = td_constr_inj_calc_discount name = `td_constr_inj_calc_discount` ). + out->write( |\n| ). + + td_constr_inj_calc_discount = td_constr_inj_calc_discount( 500 ). + out->write( data = td_constr_inj_calc_discount name = `td_constr_inj_calc_discount` ). + out->write( |\n| ). + + td_constr_inj_calc_discount = td_constr_inj_calc_discount( CONV decfloat34( '3.4' ) ). + out->write( data = td_constr_inj_calc_discount name = `td_constr_inj_calc_discount` ). + out->write( |\n| ). + + "----- 1a) Using parameter injection ----- + "Example with Sunday + td_param_inj_calc_discount( + EXPORTING + value = 100 + date = '20241201' + time = '100000' + IMPORTING + weekday = DATA(weekday) + message = DATA(message) + RECEIVING + result = DATA(td_param_inj_calc_discount) + ). + + out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ). + out->write( data = weekday name = `weekday` ). + out->write( data = message name = `message` ). + out->write( |\n| ). + + "Example with a weekday, morning + td_param_inj_calc_discount( + EXPORTING + value = 100 + date = '20241202' + time = '100000' + IMPORTING + weekday = weekday + message = message + RECEIVING + result = td_param_inj_calc_discount + ). + + out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ). + out->write( data = weekday name = `weekday` ). + out->write( data = message name = `message` ). + out->write( |\n| ). + + "Example with a weekday, afternoon + td_param_inj_calc_discount( + EXPORTING + value = 100 + date = '20241203' + time = '150000' + IMPORTING + weekday = weekday + message = message + RECEIVING + result = td_param_inj_calc_discount + ). + + out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ). + out->write( data = weekday name = `weekday` ). + out->write( data = message name = `message` ). + out->write( |\n| ). + + "Example with a weekday, night + td_param_inj_calc_discount( + EXPORTING + value = 100 + date = '20241204' + time = '230000' + IMPORTING + weekday = weekday + message = message + RECEIVING + result = td_param_inj_calc_discount + ). + + out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ). + out->write( data = weekday name = `weekday` ). + out->write( data = message name = `message` ). + out->write( |\n| ). + + "----- 2) Methods demonstrating the ABAP SQL Test Double Framework in the test include ----- + DATA(sql_shortest_flight_time) = sql_get_shortest_flight_time( 'LH' ). + out->write( data = sql_shortest_flight_time name = `sql_shortest_flight_time` ). + out->write( |\n| ). + + sql_shortest_flight_time = sql_get_shortest_flight_time( 'AA' ). + out->write( data = sql_shortest_flight_time name = `sql_shortest_flight_time` ). + out->write( |\n| ). + + "----- 3) Methods demonstrating the ABAP CDS Test Double Framework in the test include ----- + DATA(data_set_cds) = cds_get_data_set( 'LH' ). + out->write( data = data_set_cds name = `data_set_cds` ). + out->write( |\n\n| ). + + data_set_cds = cds_get_data_set( 'AA' ). + out->write( data = data_set_cds name = `data_set_cds` ). + out->write( |\n\n| ). + + "----- Methods demonstrating mocking ABAP EML APIs in the test include ----- + "Populating demo database tables + prep_dbtab_for_eml( read_op = abap_true ). + + DATA(tab_ro) = eml_read_root( key = 1 ). + out->write( tab_ro ). + out->write( |\n\n| ). + + DATA(tab_ch) = eml_rba( key = 1 ). + out->write( tab_ch ). + out->write( |\n\n| ). + + prep_dbtab_for_eml( ). + + eml_modify_root( + EXPORTING + instances = VALUE #( ( %cid = `cid1` key_field = 3 field1 = 'aaa' field2 = 'bbb' field3 = 30 field4 = 300 ) + ( %cid = `cid2` key_field = 4 field1 = 'ccc' field2 = 'ddd' field3 = 40 field4 = 400 ) ) + IMPORTING + mapped = DATA(m) + failed = DATA(f) + reported = DATA(r) + ). + + ASSERT f IS INITIAL. + ASSERT r IS INITIAL. + out->write( data = m-root name = `m-root` ). + out->write( |\n\n| ). + + SELECT * FROM zdemo_abap_rapt1 INTO TABLE @DATA(itab). + out->write( data = itab name = `itab` ). + ENDMETHOD. + + METHOD constructor. + "--------------------- 1a) --------------------- + "Demonstrating the constructor injection + "The parameter is only bound when you run the unit test. + "In that case, the test double is injected, and method calls + "in the implementations use data from the test double. + "Otherwise, a new instance is created that is used to call + "methods (a test double is not injected). + IF oref_constr_inj IS BOUND. + oref_data_provider = oref_constr_inj. + ELSE. + oref_data_provider = NEW #( ). + ENDIF. + ENDMETHOD. + + METHOD td_constr_inj_calc_discount. + "--------------------- 1a) --------------------- + "Method that demonstrates the ABAP OO Test Double Framework and + "constructor injection in ABAP Unit. + "When running the unit test, 'oref_data_provider' includes the test + "double. The method expects a numeric value. 'get_discount' returns + "another numeric value on whose basis a discount calculation is + "performed. The value returned by the 'get_discount' method depends + "on the current weekday and the UTC time. + result = ( value * oref_data_provider->get_discount( ) ) / 100. + result = value - result. + ENDMETHOD. + + METHOD td_param_inj_calc_discount. + "--------------------- 1b) --------------------- + "Method that demonstrates the ABAP OO Test Double Framework and + "parameter injection in ABAP Unit. + "When running the unit test, the optional parameter 'data_prov' is + "assigned, and therefore bound here. 'oref_data_provider' then includes + "the test double. + "The purpose of this method is similar to 'td_constr_inj_calc_discount'. + "Here, the method expects a date and time besides a numeric value. + IF data_prov IS BOUND. + oref_data_provider = data_prov. + ENDIF. + + "Getting the weekday + DATA(day_value) = ( 5 + date MOD 7 ) MOD 7 + 1. + weekday = SWITCH #( day_value + WHEN 1 THEN `Monday` + WHEN 2 THEN `Tuesday` + WHEN 3 THEN `Wednesday` + WHEN 4 THEN `Thursday` + WHEN 5 THEN `Friday` + WHEN 6 THEN `Saturday` + WHEN 7 THEN `Sunday` ). + + DATA(time_value) = COND #( WHEN time BETWEEN '000000' AND '045959' THEN 1 "Night discount + WHEN time BETWEEN '220000' AND '235959' THEN 1 "Night discount + WHEN time BETWEEN '050000' AND '115959' THEN 2 "Morning discount + WHEN time BETWEEN '180000' AND '215959' THEN 3 "Evening discount + ELSE 0 "No discount + ). + + weekday = |{ weekday } ({ SWITCH #( time_value WHEN 1 THEN `night` WHEN 2 THEN `morning` WHEN 3 THEN `evening` ELSE `afternoon` ) })|. + + DATA(disc) = oref_data_provider->get_discount_value( day_value = day_value time_value = time_value ). + result = ( value * disc ) / 100. + result = value - result. + message = |Original value: { value }; discount: { disc }; value with discount: { result }; | && + |when: { weekday }, { date DATE = ISO }, { time TIME = ISO }|. + ENDMETHOD. + + METHOD sql_get_shortest_flight_time. + "--------------------- 2) --------------------- + "When running the unit test, the DOC (database table) is replaced with a test double. + + "Getting the shortest flight time among a given carrier + SELECT MIN( fltime ) AS fltime FROM zdemo_abap_flsch WHERE carrid = @carrier INTO @shortest_flight. + ENDMETHOD. + + METHOD cds_get_data_set. + "--------------------- 3) --------------------- + "When running the unit test, the DOC (CDS view entity) is replaced with a test double. + + "Getting a line filtered by the carrier + SELECT SINGLE * FROM zdemo_abap_cds_ve_agg_exp WHERE carrid = @carrier INTO @agg_line. + ENDMETHOD. + + METHOD prep_dbtab_for_eml. + "Preparing database tables for ABAP EML statements + DELETE FROM zdemo_abap_rapt1. + + IF read_op = abap_true. + DELETE FROM zdemo_abap_rapt2. + INSERT zdemo_abap_rapt1 FROM TABLE @( VALUE #( ( key_field = 1 field1 = 'aaa' field2 = 'bbb' field3 = 10 field4 = 100 ) ) ). + INSERT zdemo_abap_rapt2 FROM TABLE @( VALUE #( ( key_field = 1 key_ch = 11 field_ch1 = 'ccc' field_ch2 = 111 ) + ( key_field = 1 key_ch = 12 field_ch1 = 'ddd' field_ch2 = 112 ) ) ). + ENDIF. + ENDMETHOD. + + METHOD eml_read_root. + "--------------------- 4a) --------------------- + "Method that includes an ABAP EML read request on the root entity + "When running the unit test, the ABAP EML API is mocked. + + READ ENTITIES OF zdemo_abap_rap_ro_m + ENTITY root + ALL FIELDS WITH VALUE #( ( key_field = key ) ) + RESULT tab_ro. + ENDMETHOD. + + METHOD eml_rba. + "--------------------- 4a) --------------------- + "Method that includes an ABAP EML read-by-assocation request + "When running the unit test, the ABAP EML API is mocked. + + READ ENTITIES OF zdemo_abap_rap_ro_m + ENTITY root + BY \_child + ALL FIELDS WITH VALUE #( ( key_field = key ) ) + RESULT tab_ch. + ENDMETHOD. + + METHOD eml_modify_root. + "--------------------- 4a) --------------------- + "Method that includes an ABAP EML create request + "When running the unit test, the ABAP EML API is mocked. + + MODIFY ENTITIES OF zdemo_abap_rap_ro_m + ENTITY root + CREATE + FIELDS ( key_field field1 field2 field3 field4 ) + WITH instances + MAPPED mapped + FAILED failed + REPORTED reported. + + COMMIT ENTITIES. + ENDMETHOD. + + METHOD eml_read_root_buffer_td. + "--------------------- 4b) --------------------- + "Method that includes an ABAP EML read request + "When running the unit test, a transactional buffer test double is + "included. + + READ ENTITIES OF zdemo_abap_rap_ro_u + ENTITY root + ALL FIELDS WITH VALUE #( ( key_field = key ) ) + RESULT tab_ro_u. + ENDMETHOD. + +ENDCLASS. diff --git a/src/zcl_demo_abap_unit_tdf.clas.xml b/src/zcl_demo_abap_unit_tdf.clas.xml new file mode 100644 index 0000000..912356b --- /dev/null +++ b/src/zcl_demo_abap_unit_tdf.clas.xml @@ -0,0 +1,16 @@ + + + + + + ZCL_DEMO_ABAP_UNIT_TDF + E + ABAP cheat sheet: Creating Test Doubles Using ABAP Framework + 1 + X + X + X + + + + diff --git a/src/zcl_demo_abap_xml_json.clas.abap b/src/zcl_demo_abap_xml_json.clas.abap index 4250aee..c6e4772 100644 --- a/src/zcl_demo_abap_xml_json.clas.abap +++ b/src/zcl_demo_abap_xml_json.clas.abap @@ -47,6 +47,7 @@ CLASS zcl_demo_abap_xml_json DEFINITION deserialize_helper IMPORTING attr_string_a TYPE string attr_string_b TYPE string attr_concat_string TYPE string. + ENDCLASS. @@ -144,7 +145,7 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. ( ReleasedObjectName LIKE '%IXML%' OR ReleasedObjectName LIKE '%SXML%' ) INTO TABLE @DATA(released_xml_libs). - out->write( `No output. You can check the internal table content in the debugger to view the usable artifacts.` ). + out->write( zcl_demo_abap_aux=>no_output ). *********************************************************************** @@ -196,29 +197,27 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. *********************************************************************** out->write( zcl_demo_abap_aux=>heading( `4) Parsing XML Data Using iXML` ) ). - "The example covers the following aspects: - "- Parsing XML data to a DOM object in one go - "- Directly reading nodes using various iXML methods - "- Directly reading nodes using element names - "- Reading using iterators, i.e. going over the XML nodes one after another - "- During the iteration, ... - " ... node properties are extracted using various iXML methods - " (and stored in an internal table for display purposes). - " ... the XML data is modified. - " ... a new element is created. - "- Rendering XML data + "Notes on the example: + "- XML data is transformed to an input steam object and imported to a DOM object in one go using a parser object + "- If the XML is successfully parsed, DOM object nodes are iterated and accessed + "- Elements of the XML data and their attributes are processed, read and output + "- The create_renderer method is used to render XML data into an output stream - "Internal table to store node properties for display purposes - DATA properties TYPE string_table. - - "Creating simple demo XML data to be used in the example TRY. DATA(some_xml) = cl_abap_conv_codepage=>create_out( )->convert( - `` && - ` hallo` && - ` how` && - ` are` && - `` ). + `` && + `` && + ` ` && + ` A` && + ` 01-01-2024` && + ` ` && + ` ` && + ` abc` && + ` def` && + ` ghi` && + ` jkl` && + ` ` && + `` ). CATCH cx_sy_conversion_codepage. ENDTRY. @@ -237,77 +236,59 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. document = document_pa stream_factory = stream_factory_pa ). - "Parsing XML data to a DOM representation in one go. It is put in the memory. - "Note: You can also parse sequentially, and not in one go. - DATA(parsing_check) = parser_pa->parse( ). - IF parsing_check = 0. "Parsing was successful + IF parser_pa->parse( ) <> 0. + "Processing errors + DATA(error_pa_num) = parser_pa->num_errors( ). - "Directly reading nodes using various iXML methods - - "Accessing the root element of the DOM. It can be used as the initial node - "for accessing subnodes. - "You can check the content of the variables in the debugger. - "Note: Multiple methods are available to further process the nodes. - DATA(root_element) = document_pa->get_root_element( ). - "First subnode - DATA(child_element) = root_element->get_first_child( ). - "Getting the value of that node - DATA(child_element_value) = child_element->get_value( ). - "Next adjacent node/getting the value - DATA(next_element_value) = child_element->get_next( )->get_value( ). - - "Directly reading nodes using element names - "The result is the first element searched for. - DATA(element_by_name) = document_pa->find_from_name( name = `word3` )->get_value( ). - "A lot more options are available such as access by attributes. - - "Reading using iterators, i.e. going over the XML nodes sequentially - - "Creating an iterator - DATA(iterator_pa) = document_pa->create_iterator( ). - DO. - "For the iteration, you can use the get_next method to process the nodes one after another. - "Note: Here, all nodes are respected. You can also create filters to go over specific nodes. - DATA(node_i) = iterator_pa->get_next( ). - IF node_i IS INITIAL. - EXIT. - ELSE. - "Extracting properties - "For display purposes, the properties are stored in an internal table. - APPEND |gid: { node_i->get_gid( ) } / type: { node_i->get_type( ) } / name: { node_i->get_name( ) } / value: { node_i->get_value( ) }| TO properties. - ENDIF. - - IF node_i->get_type( ) = if_ixml_node=>co_node_text. - "Modifying values - "Here, the values are capitalized. - node_i->set_value( to_upper( node_i->get_value( ) ) ). - ENDIF. - - "Creating a new element - IF node_i->get_value( ) = 'are'. - document_pa->create_simple_element_ns( name = 'word4' - value = 'you' - parent = node_i->get_parent( ) ). - ENDIF. + DO error_pa_num TIMES. + DATA(error_pa) = parser_pa->get_error( + index = sy-index - 1 + min_severity = 3 + ). + DATA(reason_pa) = error_pa->get_reason( ). + out->write( |Error: { reason_pa }| ). ENDDO. - - "Creating a renderer - DATA xml_pa TYPE xstring. - ixml_pa->create_renderer( document = document_pa - ostream = ixml_pa->create_stream_factory( )->create_ostream_xstring( string = xml_pa ) - )->render( ). - - "Getting XML - DATA(output_ixml_parsing) = format( cl_abap_conv_codepage=>create_in( )->convert( xml_pa ) ). - - out->write( output_ixml_parsing ). - out->write( |\n| ). - out->write( `Node properties:` ). - out->write( properties ). ELSE. - out->write( `Parsing was not successful.` ). + "Processing document + IF document_pa IS NOT INITIAL. + DATA(iterator_pa) = document_pa->create_iterator( ). + DATA(node_pa) = iterator_pa->get_next( ). + WHILE NOT node_pa IS INITIAL. + DATA(indent) = node_pa->get_height( ) * 2. + "Rettrieving the node type + CASE node_pa->get_type( ). + WHEN if_ixml_node=>co_node_element. + "Retrieving attributes + DATA(attributes_pa) = node_pa->get_attributes( ). + out->write( |Element:{ repeat( val = ` ` occ = indent + 2 ) }{ node_pa->get_name( ) }| ). + IF NOT attributes_pa IS INITIAL. + DO attributes_pa->get_length( ) TIMES. + DATA(attr) = attributes_pa->get_item( sy-index - 1 ). + out->write( |Attribute:{ repeat( val = ` ` occ = indent ) }{ attr->get_name( ) } = { attr->get_value( ) } | ). + ENDDO. + ENDIF. + WHEN if_ixml_node=>co_node_text OR + if_ixml_node=>co_node_cdata_section. + out->write( |Text:{ repeat( val = ` ` occ = indent + 5 ) }{ node_pa->get_value( ) }| ). + ENDCASE. + "Retrieving the next node + node_pa = iterator_pa->get_next( ). + ENDWHILE. + ENDIF. ENDIF. + "Creating a renderer + DATA xml_pa TYPE xstring. + ixml_pa->create_renderer( document = document_pa + ostream = ixml_pa->create_stream_factory( )->create_ostream_xstring( string = xml_pa ) + )->render( ). + + "Getting XML + DATA(output_ixml_parsing) = cl_abap_conv_codepage=>create_in( )->convert( xml_pa ). + + out->write( |\n| ). + out->write( output_ixml_parsing ). + *********************************************************************** out->write( zcl_demo_abap_aux=>heading( `5) Creating XML Data Using sXML (Token-Based Rendering)` ) ). @@ -426,13 +407,9 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. "Creating an internal table for display purposes DATA: BEGIN OF node_info, - node_type TYPE string, - prefix TYPE string, - name TYPE string, - nsuri TYPE string, - value_type TYPE string, - value TYPE string, - value_raw TYPE xstring, + node_type TYPE string, + name TYPE string, + value TYPE string, END OF node_info, nodes_tab LIKE TABLE OF node_info. @@ -446,8 +423,7 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. "To iterate accros all nodes, you can call the NEXT_NODE method. TRY. DO. - "Check out other available methods in ADT by placing the cursor behind -> - "and choosing CTRL + Space. + CLEAR node_info. reader->next_node( ). "When reaching the end of the XML data, the loop is exited. @@ -457,39 +433,21 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. "You can access the properties of the node directly. "For display purposes, the property information is stored in an internal table. - "The demo XML data that is used here does not include all properties. Therefore, - "the values for these are initial. + "The example here just uses simple demo JSON data. Not all properties are + "retrieved and displayed. "Node type, see the interface if_sxml_node - DATA(node_type) = SWITCH #( reader->node_type WHEN if_sxml_node=>co_nt_initial THEN `CO_NT_INITIAL` - WHEN if_sxml_node=>co_nt_element_open THEN `CO_NT_ELEMENT_OPEN` - WHEN if_sxml_node=>co_nt_element_close THEN `CO_NT_ELEMENT_CLOSE` - WHEN if_sxml_node=>co_nt_value THEN `CO_NT_VALUE` - WHEN if_sxml_node=>co_nt_attribute THEN `CO_NT_ATTRIBUTE` - ELSE `Error` ). - - DATA(prefix) = reader->prefix. "Namespace prefix - DATA(name) = reader->name. "Name of the element - DATA(nsuri) = reader->nsuri. "Namespace URI - - "Value type, see the interface if_sxml_value - DATA(value_type) = SWITCH #( reader->value_type WHEN 0 THEN `Initial` - WHEN if_sxml_value=>co_vt_none THEN `CO_VT_NONE` - WHEN if_sxml_value=>co_vt_text THEN `CO_VT_TEXT` - WHEN if_sxml_value=>co_vt_raw THEN `CO_VT_RAW` - WHEN if_sxml_value=>co_vt_any THEN `CO_VT_ANY` - ELSE `Error` ). - - DATA(value) = reader->value. "Character-like value (if it is textual data) - DATA(value_raw) = reader->value_raw. "Byte-like value (if it is raw data) - - APPEND VALUE #( node_type = node_type - prefix = prefix - name = name - nsuri = nsuri - value_type = value_type - value = value - value_raw = value_raw ) TO nodes_tab. + node_info-node_type = SWITCH #( reader->node_type WHEN if_sxml_node=>co_nt_initial THEN `CO_NT_INITIAL` + WHEN if_sxml_node=>co_nt_element_open THEN `CO_NT_ELEMENT_OPEN` + WHEN if_sxml_node=>co_nt_element_close THEN `CO_NT_ELEMENT_CLOSE` + WHEN if_sxml_node=>co_nt_value THEN `CO_NT_VALUE` + WHEN if_sxml_node=>co_nt_attribute THEN `CO_NT_ATTRIBUTE` + ELSE `Error` ). + "Name of the element + node_info-name = reader->name. + "Character-like value (if it is textual data) + node_info-value = COND #( WHEN reader->node_type = if_sxml_node=>co_nt_value THEN reader->value ). + APPEND node_info TO nodes_tab. "Once the method is called, you can directly access the attributes of the reader with the required "properties of the node. When the parser is on the node of an element opening, you can use the method @@ -500,22 +458,18 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. IF reader->node_type <> if_sxml_node=>co_nt_attribute. EXIT. ENDIF. - APPEND VALUE #( node_type = `attribute` - prefix = reader->prefix + APPEND VALUE #( node_type = `CO_NT_ATTRIBUTE` name = reader->name - nsuri = reader->nsuri - value = reader->value - value_raw = reader->value_raw ) TO nodes_tab. + value = reader->value ) TO nodes_tab. ENDDO. ENDIF. ENDDO. + + out->write( nodes_tab ). CATCH cx_sxml_state_error INTO DATA(error_parse_token). out->write( error_parse_token->get_text( ) ). ENDTRY. - out->write( `Node properties:` ). - out->write( nodes_tab ). - *********************************************************************** out->write( zcl_demo_abap_aux=>heading( `8) Parsing XML Data using sXML (Object-Oriented Parsing)` ) ). @@ -551,20 +505,15 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. DATA(open_element) = CAST if_sxml_open_element( node_oo ). APPEND VALUE #( node_type = `open element` - prefix = open_element->prefix name = open_element->qname-name - nsuri = open_element->qname-namespace ) TO nodes_tab. DATA(attributes) = open_element->get_attributes( ). LOOP AT attributes INTO DATA(attribute). APPEND VALUE #( node_type = `attribute` - prefix = open_element->prefix name = open_element->qname-name - nsuri = open_element->qname-namespace value = SWITCH #( attribute->value_type WHEN if_sxml_value=>co_vt_text THEN attribute->get_value( ) ) - value_raw = SWITCH #( attribute->value_type WHEN if_sxml_value=>co_vt_raw THEN attribute->get_value_raw( ) ) ) TO nodes_tab. ENDLOOP. @@ -572,9 +521,7 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. DATA(close_element) = CAST if_sxml_close_element( node_oo ). APPEND VALUE #( node_type = `close element` - prefix = open_element->prefix name = open_element->qname-name - nsuri = open_element->qname-namespace ) TO nodes_tab. WHEN if_sxml_node=>co_nt_value. @@ -582,7 +529,6 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. APPEND VALUE #( node_type = `value` value = SWITCH #( value_node_oo->value_type WHEN if_sxml_value=>co_vt_text THEN value_node_oo->get_value( ) ) - value_raw = SWITCH #( value_node_oo->value_type WHEN if_sxml_value=>co_vt_raw THEN value_node_oo->get_value_raw( ) ) ) TO nodes_tab. WHEN OTHERS. @@ -593,7 +539,6 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. out->write( error_parse_oo->get_text( ) ). ENDTRY. - out->write( `Node properties:` ). out->write( nodes_tab ). *********************************************************************** @@ -1223,7 +1168,7 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. ************************************************************************ - out->write( zcl_demo_abap_aux=>heading( `22) Dealing with JSON Data` ) ). + out->write( zcl_demo_abap_aux=>heading( `22) Transforming JSON Data Using Transformations` ) ). "Note: When the identity transformation ID is used, the format is asJSON. "Elementary type @@ -1391,18 +1336,18 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. ************************************************************************ - out->write( zcl_demo_abap_aux=>heading( `23) XCO Classes for JSON` ) ). + out->write( zcl_demo_abap_aux=>heading( `23) Handling JSON Data with XCO Classes` ) ). "Note: Unlike above, the following snippets do not work with asJSON as intermediate "format. DATA: BEGIN OF carrier_struc, - carrier_id TYPE c length 3, - connection_id TYPE n length 4, - city_from TYPE c length 20, - city_to TYPE c length 20, + carrier_id TYPE c LENGTH 3, + connection_id TYPE n LENGTH 4, + city_from TYPE c LENGTH 20, + city_to TYPE c LENGTH 20, END OF carrier_struc. - DATA carriers_tab like TABLE OF carrier_struc WITH EMPTY KEY. + DATA carriers_tab LIKE TABLE OF carrier_struc WITH EMPTY KEY. carrier_struc = VALUE #( carrier_id = 'AA' connection_id = '17' city_from = 'New York' city_to = 'San Francisco' ). carriers_tab = VALUE #( ( carrier_id = 'AZ' connection_id = '788' city_from = 'Rome' city_to = 'Tokyo' ) @@ -1467,7 +1412,131 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. ************************************************************************ - out->write( zcl_demo_abap_aux=>heading( `24) Excursion: Compressing and Decompressing Binary Data` ) ). + out->write( zcl_demo_abap_aux=>heading( `24) Handling JSON Data with the /ui2/cl_json Class` ) ). + + TYPES: BEGIN OF demo_struc, + carrier_id TYPE c LENGTH 3, + connection_id TYPE n LENGTH 4, + city_from TYPE c LENGTH 20, + city_to TYPE c LENGTH 20, + END OF demo_struc. + DATA itab TYPE TABLE OF demo_struc WITH EMPTY KEY. + itab = VALUE #( ( carrier_id = 'AA' connection_id = '0017' city_from = 'New York' city_to = 'San Francisco' ) + ( carrier_id = 'AZ' connection_id = '0789' city_from = 'Tokyo' city_to = 'Rome' ) ). + + "---------------- Serializing ---------------- + + DATA(abap_to_json) = /ui2/cl_json=>serialize( data = itab ). + "Note the many additional, optional parameters such as for formatting the + "serialized JSON. For more information, see the class documentation. + DATA(abap_to_json_pretty) = /ui2/cl_json=>serialize( data = itab + format_output = abap_true ). + DATA(abap_to_json_pretty_name) = /ui2/cl_json=>serialize( data = itab + format_output = abap_true + pretty_name = /ui2/cl_json=>pretty_mode-camel_case ). + + out->write( `---------- ABAP -> JSON ----------` ). + out->write( abap_to_json ). + out->write( |\n| ). + out->write( `---------- ABAP -> JSON (pretty printed) ----------` ). + out->write( abap_to_json_pretty ). + out->write( |\n| ). + out->write( `---------- ABAP -> JSON (camel case) ----------` ). + out->write( abap_to_json_pretty_name ). + out->write( |\n| ). + + "---------------- Deserializing ---------------- + + DATA(json_to_abap) = abap_to_json. + DATA itab_json_to_abap LIKE itab. + + /ui2/cl_json=>deserialize( EXPORTING json = json_to_abap + CHANGING data = itab_json_to_abap ). + + out->write( `---------- JSON -> ABAP ----------` ). + out->write( itab_json_to_abap ). + out->write( |\n| ). + + "---------------- Deserializing: Applying name mapping ---------------- + "Creating an internal table with different field names + TYPES: BEGIN OF demo_struc4map, + carr TYPE c LENGTH 3, + conn TYPE n LENGTH 4, + from TYPE c LENGTH 20, + to TYPE c LENGTH 20, + END OF demo_struc4map. + DATA itab4map TYPE TABLE OF demo_struc4map WITH EMPTY KEY. + + /ui2/cl_json=>deserialize( EXPORTING json = json_to_abap + name_mappings = VALUE #( ( abap = 'CARR' json = `CARRIER_ID` ) + ( abap = 'CONN' json = `CONNECTION_ID` ) + ( abap = 'FROM' json = `CITY_FROM` ) + ( abap = 'TO' json = `CITY_TO` ) ) + CHANGING data = itab4map ). + + out->write( `---------- JSON -> ABAP (Name mapping) ----------` ). + out->write( itab4map ). + out->write( |\n| ). + + "---------------- Deserializing: Using JSON as xstring ---------------- + + DATA(json_xstring) = cl_abap_conv_codepage=>create_out( )->convert( + `[` && + ` {` && + ` "carrier_id": "LH",` && + ` "connection_id": "400",` && + ` "city_from": "Frankfurt",` && + ` "city_to": "Berlin"` && + ` },` && + ` {` && + ` "carrier_id": "DL",` && + ` "connection_id": "1984",` && + ` "city_from": "San Francisco",` && + ` "city_to": "New York"` && + ` },` && + ` {` && + ` "carrier_id": "AZ",` && + ` "connection_id": "790",` && + ` "city_from": "Rome",` && + ` "city_to": "Osaka"` && + ` }` && + `]` ). + + DATA itab_json_xstr_to_abap LIKE itab. + /ui2/cl_json=>deserialize( EXPORTING jsonx = json_xstring + CHANGING data = itab_json_xstr_to_abap ). + + out->write( `---------- JSON (xstring) -> ABAP ----------` ). + out->write( itab_json_xstr_to_abap ). + out->write( |\n| ). + + "---------------- Deserializing: No equivalent ABAP type available ---------------- + + "The example assumes that there is no equivalent ABAP type available for JSON data + "that is to be deserialized. You can use the 'generate' method that has a + "returning parameter of the generic type 'ref to data'. + DATA(json) = `[{"CARRIER_ID":"AA","CONNECTION_ID":17,"CITY_FROM":"New York","CITY_TO":"San Francisco"},` && + `{"CARRIER_ID":"AZ","CONNECTION_ID":789,"CITY_FROM":"Tokyo","CITY_TO":"Rome"}]`. + + DATA(dref) = /ui2/cl_json=>generate( json = json ). + DATA(dref_xstr) = /ui2/cl_json=>generate( jsonx = json_xstring ). + + "You can further process the content, for example, with RTTS as outlined in the + "Dynamic Programming cheat sheet. + IF dref IS BOUND. + out->write( `---------- JSON -> ABAP (unknown type) ----------` ). + out->write( dref->* ). + out->write( |\n| ). + ENDIF. + + IF dref_xstr IS BOUND. + out->write( `---------- JSON (xstring) -> ABAP (unknown type) ----------` ). + out->write( dref_xstr->* ). + ENDIF. + +************************************************************************ + + out->write( zcl_demo_abap_aux=>heading( `25) Excursion: Compressing and Decompressing Binary Data` ) ). "You may want to process or store binary data. The data can be very large. "You can compress the data in gzip format and decompress it for further processing using "the cl_abap_gzip class. Check out appropriate exceptions to be caught. The simple example @@ -1503,6 +1572,7 @@ CLASS zcl_demo_abap_xml_json IMPLEMENTATION. IF xml_oref_a = xstr_decomp. out->write( `The decompressed binary data object has the same value as the original binary data object.` ). ENDIF. + ENDMETHOD. METHOD format. TRY. diff --git a/src/ztcl_demo_abap_unit_tdf_testcl.clas.abap b/src/ztcl_demo_abap_unit_tdf_testcl.clas.abap new file mode 100644 index 0000000..8175c33 --- /dev/null +++ b/src/ztcl_demo_abap_unit_tdf_testcl.clas.abap @@ -0,0 +1,25 @@ +"!

Test Class Supporting an ABAP Unit Test Example
ABAP cheat sheet example class

+"! +"!

The example class {@link zcl_demo_abap_unit_tdf} demonstrates managing dependencies (dependent-on-components, DOC) +"! with ABAP Unit and explores the creation of test doubles using ABAP frameworks.
+"! This global class is empty, but the the Test Classes tab includes a test class. Using the syntax +"! "! @testing ..., a test relation between this test class and another global class +"! ({@link zcl_demo_abap_unit_tdf}) is defined. Therefore, when you run unit tests of class {@link zcl_demo_abap_unit_tdf}, +"! the tests contained in the class {@link ztcl_demo_abap_unit_tdf_testcl} are executed.

+"! +"!

Information

+"!

Find information on getting started with the example class and the disclaimer in +"! the ABAP Doc comment of class {@link zcl_demo_abap_aux}.

+CLASS ztcl_demo_abap_unit_tdf_testcl DEFINITION + PUBLIC + FINAL + CREATE PUBLIC . + + PUBLIC SECTION. + PROTECTED SECTION. + PRIVATE SECTION. +ENDCLASS. + +CLASS ztcl_demo_abap_unit_tdf_testcl IMPLEMENTATION. + +ENDCLASS. diff --git a/src/ztcl_demo_abap_unit_tdf_testcl.clas.testclasses.abap b/src/ztcl_demo_abap_unit_tdf_testcl.clas.testclasses.abap new file mode 100644 index 0000000..d0ff5af --- /dev/null +++ b/src/ztcl_demo_abap_unit_tdf_testcl.clas.testclasses.abap @@ -0,0 +1,551 @@ +"!@testing zcl_demo_abap_unit_tdf +CLASS ltcl_test DEFINITION FINAL FOR TESTING + DURATION SHORT + RISK LEVEL HARMLESS. + + PRIVATE SECTION. + + "------------------- Local test class ------------------- + + "Object reference variable for class under test + CLASS-DATA cut TYPE REF TO zcl_demo_abap_unit_tdf. + + "----- 1) ABAP OO Test Double Framework ------ + "Test method that demonstrates constructor injection + METHODS test_tdf_constr_inj_get_discnt FOR TESTING. + + "Test method that demonstrates parameter injection + METHODS test_tdf_param_inj_get_discnt FOR TESTING. + + "----------- 2) ABAP SQL Test Double Framework ----------- + METHODS test_sql_get_shortest_flight FOR TESTING RAISING cx_static_check. + CLASS-DATA sql_env TYPE REF TO if_osql_test_environment. + + "----------- 3) ABAP CDS Test Double Framework----------- + METHODS test_select_cds FOR TESTING RAISING cx_static_check. + METHODS test_cds_standalone FOR TESTING RAISING cx_static_check. + METHODS prepare_testdata_set_cds. + CLASS-DATA cds_env TYPE REF TO if_cds_test_environment. + DATA zdemo_abap_fli_tab TYPE TABLE OF zdemo_abap_fli WITH EMPTY KEY. + + "----------- 4) Managing dependencies on RAP business objects ----------- + CONSTANTS entity TYPE abp_entity_name VALUE 'ZDEMO_ABAP_RAP_RO_M'. + CLASS-DATA eml_env TYPE REF TO if_botd_mockemlapi_bo_test_env. + "--- 4a) Test methods that demonstrate mocking EML APIs --- + METHODS test_eml_read_root FOR TESTING. + METHODS test_eml_rba FOR TESTING. + METHODS test_eml_modify_root FOR TESTING. + DATA td_eml TYPE REF TO if_botd_mockemlapi_test_double. + + "--- 4b) Test method that demonstrates transactional buffer test doubles --- + METHODS test_eml_read_root_buffer_td FOR TESTING. + + CLASS-DATA buffer_env TYPE REF TO if_botd_txbufdbl_bo_test_env. + + "----------- Test fixture ----------- + "Creating a common start state for each test method, clearing doubles + METHODS setup RAISING cx_static_check. + "Includes the creation of test environments + CLASS-METHODS class_setup RAISING cx_static_check. + "Clearing generated test doubles + CLASS-METHODS class_teardown. +ENDCLASS. + + +CLASS ltcl_test IMPLEMENTATION. + + METHOD class_setup. + "Creating an instance for the class under test + cut = NEW zcl_demo_abap_unit_tdf( ). + + "Creating instances of test environments for the test execution + "------ 2) SQL ------ + sql_env = cl_osql_test_environment=>create( + i_dependency_list = VALUE #( ( 'zdemo_abap_flsch' ) ) ). + + "------ 3) CDS ------ + cds_env = cl_cds_test_environment=>create( i_for_entity = 'zdemo_abap_cds_ve_agg_exp' + test_associations = 'X' ). + + "------ 4) ABAP EML ------ + "4a) Mocking ABAP EML APIs + eml_env = cl_botd_mockemlapi_bo_test_env=>create( + environment_config = cl_botd_mockemlapi_bo_test_env=>prepare_environment_config( + )->set_bdef_dependencies( bdef_dependencies = VALUE #( ( 'ZDEMO_ABAP_RAP_RO_M' ) ) ) ). + + "4b) Transactional buffer test doubles + buffer_env = cl_botd_txbufdbl_bo_test_env=>create( + environment_config = cl_botd_txbufdbl_bo_test_env=>prepare_environment_config( + )->set_bdef_dependencies( bdef_dependencies = VALUE #( ( 'ZDEMO_ABAP_RAP_RO_U' ) ) ) ). + + ENDMETHOD. + + METHOD class_teardown. + "Clearing generated test double at the end of the test execution + sql_env->destroy( ). + cds_env->destroy( ). + eml_env->destroy( ). + buffer_env->destroy( ). + ENDMETHOD. + + METHOD setup. + sql_env->clear_doubles( ). + cds_env->clear_doubles( ). + eml_env->clear_doubles( ). + buffer_env->clear_doubles( ). + ENDMETHOD. + + METHOD test_sql_get_shortest_flight. + "--- 2) --- + "Preparing and inserting test data + DATA test_data TYPE TABLE OF zdemo_abap_flsch WITH EMPTY KEY. + test_data = VALUE #( + ( carrid = 'LH' + connid = 0401 + fltime = 435 ) + ( carrid = 'LH' + connid = 0402 + fltime = 455 ) + ( carrid = 'LH' + connid = 2402 + fltime = 65 ) ). + + sql_env->insert_test_data( test_data ). + + "Calling method of the class under test + DATA carrier TYPE zdemo_abap_flsch-carrid VALUE 'LH'. + DATA(result) = cut->sql_get_shortest_flight_time( carrier ). + + "Verifying result + cl_abap_unit_assert=>assert_equals( + act = result + exp = 65 + msg = `Not the shortest flight` + quit = if_abap_unit_constant=>quit-no ). + ENDMETHOD. + + METHOD prepare_testdata_set_cds. + "Preparing and inserting test data + zdemo_abap_fli_tab = VALUE #( + ( carrid = 'XX' + connid = 0407 + fldate = '20231128' + price = '1102.77' + currency = 'JPY' + planetype = 'A380-800' + seatsmax = 475 + seatsocc = 458 + paymentsum = '563231.65' + seatsmax_b = 30 + seatsocc_b = 27 + seatsmax_f = 20 + seatsocc_f = 20 ) + ( carrid = 'XX' + connid = 0407 + fldate = '20231019' + price = '1102.77' + currency = 'JPY' + planetype = 'A380-800' + seatsmax = 475 + seatsocc = 452 + paymentsum = '553552.12' + seatsmax_b = 30 + seatsocc_b = 28 + seatsmax_f = 20 + seatsocc_f = 19 ) + ( carrid = 'XX' + connid = 0408 + fldate = '20231128' + price = '1102.77' + currency = 'JPY' + planetype = '747-400' + seatsmax = 385 + seatsocc = 365 + paymentsum = '470129.20' + seatsmax_b = 31 + seatsocc_b = 28 + seatsmax_f = 21 + seatsocc_f = 20 ) + ( carrid = 'XX' + connid = 0408 + fldate = '20230123' + price = '1102.77' + currency = 'JPY' + planetype = '747-400' + seatsmax = 385 + seatsocc = 372 + paymentsum = '487715.90' + seatsmax_b = 31 + seatsocc_b = 31 + seatsmax_f = 21 + seatsocc_f = 20 ) ). + + cds_env->insert_test_data( i_data = zdemo_abap_fli_tab ). + ENDMETHOD. + + METHOD test_select_cds. + "--- 3) --- + "Preparing and inserting test data + prepare_testdata_set_cds( ). + + "Calling method of the class under test + DATA carrier TYPE zdemo_abap_fli-carrid VALUE 'XX'. + DATA(act_result) = cut->cds_get_data_set( carrier ). + + "Verifying result + cl_abap_unit_assert=>assert_equals( + act = act_result + exp = VALUE zdemo_abap_cds_ve_agg_exp( carrid = 'XX' currency = 'JPY' avg_seats_occ = '411.75' + avg_paysum = '518657.22' total_paysum = '2074628.87' + min_occ_seats = 365 max_occ_seats = 458 + max_occ_seats_all = 458 cnt = 4 cnt_planetype = 2 ) + msg = `The values do not match the expected result` + quit = if_abap_unit_constant=>quit-no ). + ENDMETHOD. + + METHOD test_eml_read_root. + "--- 4a) --- + "Mocking ABAP EML API (read request) + + "Preparing test data (RAP BO instances) + DATA read_tab_ro_import TYPE TABLE FOR READ IMPORT zdemo_abap_rap_ro_m. + DATA read_tab_ro_result TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m. + read_tab_ro_import = VALUE #( ( key_field = 2 ) ). + read_tab_ro_result = VALUE #( ( key_field = 2 field1 = 'uuu' field2 = 'vvv' field3 = 20 field4 = 200 ) ). + + "Configuring input and output for the ABAP EML read request + "Creating input/output configuration builders + DATA(input_config_read) = cl_botd_mockemlapi_bldrfactory=>get_input_config_builder( )->for_read( ). + DATA(output_config_read) = cl_botd_mockemlapi_bldrfactory=>get_output_config_builder( )->for_read( ). + + "Creating input for the entities (here, only one entity is included) + "Instead of the entity name, an alias name is also accepted. + DATA(eml_read_input) = input_config_read->build_entity_part( entity + )->set_instances_for_read( read_tab_ro_import ). + + "Input configuration + DATA(input) = input_config_read->build_input_for_eml( )->add_entity_part( eml_read_input ). + + "Output configuration + DATA(output) = output_config_read->build_output_for_eml( )->set_result_for_read( read_tab_ro_result ). + + "Configuring the RAP BO test double + td_eml = eml_env->get_test_double( entity ). + td_eml->configure_call( )->for_read( )->when_input( input )->then_set_output( output ). + + "Calling method (containing the ABAP EML read request) of the class under test + DATA(t_read_res_ro) = cut->eml_read_root( key = 2 ). + + "Verifying the result + cl_abap_unit_assert=>assert_equals( + act = lines( t_read_res_ro ) + exp = 1 + msg = `The number of lines does not match` + quit = if_abap_unit_constant=>quit-no ). + + TYPES struc_read_ro TYPE STRUCTURE FOR READ RESULT zdemo_abap_rap_ro_m. + cl_abap_unit_assert=>assert_equals( + act = t_read_res_ro[ 1 ] + exp = VALUE struc_read_ro( key_field = 2 field1 = 'uuu' field2 = 'vvv' field3 = 20 field4 = 200 ) + msg = `The values do not match the expected result` + quit = if_abap_unit_constant=>quit-no ). + ENDMETHOD. + + METHOD test_tdf_constr_inj_get_discnt. + "--- 1a) --- + "Method that demonstrates the ABAP OO Test Double Framework + "Injection mechanism: Constructor injection + + "Creating a test double + DATA(test_double) = CAST zcl_demo_abap_unit_dataprov( + cl_abap_testdouble=>create( 'zcl_demo_abap_unit_dataprov' ) ). + + "Configuring a method call + "It is used for the next method call (the actual method calling) + "In this example, the method only has a returning parameter. Check + "the class documentation for more static methods of the cl_abap_testdouble + "class, e.g. for configuring other parameters. + cl_abap_testdouble=>configure_call( test_double + )->returning( 5 ). + + "Calling method of the external class + test_double->get_discount( ). + + "Injecting the test double + "Here, the instance constructor is provided with the test double + "instance + cut = NEW #( test_double ). + + "Calling method of the class under test + DATA(result) = cut->td_constr_inj_calc_discount( 500 ). + + "Verifying the result + cl_abap_unit_assert=>assert_equals( + act = result + exp = 475 + msg = `The values do not match the expected result` + quit = if_abap_unit_constant=>quit-no ). + ENDMETHOD. + + METHOD test_tdf_param_inj_get_discnt. + "--- 1b) --- + "Method that demonstrates the ABAP OO Test Double Framework + "Injection mechanism: Parameter injection + + "Creating a test double + DATA(test_double_param_inj) = CAST zcl_demo_abap_unit_dataprov( + cl_abap_testdouble=>create( 'zcl_demo_abap_unit_dataprov' ) ). + + "Configuring a method call, ignoring importing parameters + cl_abap_testdouble=>configure_call( test_double_param_inj + )->ignore_parameter( 'DAY_VALUE' + )->ignore_parameter( 'TIME_VALUE' + )->returning( 40 ). + + "Calling method of the external class + "Dummy values are provided for the non-optional importing + "parameters (the returning value is determined above) + test_double_param_inj->get_discount_value( + EXPORTING + day_value = 99 + time_value = 99 + ). + + "Calling method of the class under test + "In this case, the optional parameter is assigned the + "test double replacing the DOC + cut->td_param_inj_calc_discount( + EXPORTING + value = 100 + date = '20241201' "Sunday + time = '100000' + data_prov = test_double_param_inj + IMPORTING + message = DATA(msg) + weekday = DATA(weekday) + RECEIVING + result = DATA(result) + ). + + "Verifying the result + cl_abap_unit_assert=>assert_equals( + act = result + exp = 60 + msg = `The values do not match the expected result` + quit = if_abap_unit_constant=>quit-no ). + ENDMETHOD. + + METHOD test_eml_rba. + "--- 4a) --- + "Method that demonstrates mocking an ABAP EML API (read-by-association request) + + "Preparing test data (RAP BO instances) + DATA read_tab_ch_import TYPE TABLE FOR READ IMPORT zdemo_abap_rap_ro_m\_child. + DATA read_tab_ch_result TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m\_child. + + read_tab_ch_import = VALUE #( ( key_field = 2 ) ). + + read_tab_ch_result = VALUE #( ( key_field = 2 key_ch = 22 field_ch1 = 'www' field_ch2 = 222 ) + ( key_field = 2 key_ch = 23 field_ch1 = 'xxx' field_ch2 = 223 ) ). + + "Configuring input and output for the ABAP EML read-by-association request + DATA(input_config_rba) = cl_botd_mockemlapi_bldrfactory=>get_input_config_builder( )->for_read( ). + DATA(output_config_rba) = cl_botd_mockemlapi_bldrfactory=>get_output_config_builder( )->for_read( ). + + "Creating input for the entities (here, only one entity is included) + DATA(eml_rba_input) = input_config_rba->build_entity_part( entity + )->set_instances_for_read_ba( read_tab_ch_import ). + + "Input configuration + DATA(input_rba) = input_config_rba->build_input_for_eml( )->add_entity_part( eml_rba_input ). + + "Output configuration + DATA(output_rba) = output_config_rba->build_output_for_eml( + )->set_result_for_read_ba( + source_entity_name = entity + assoc_name = '_CHILD' + result = read_tab_ch_result ). + + "Configuring the RAP BO test double + td_eml = eml_env->get_test_double( entity ). + td_eml->configure_call( )->for_read( )->when_input( input_rba )->then_set_output( output_rba ). + + "Calling method (containing the ABAP EML read-by-association request) + "of the class under test + DATA(t_read_res_ch) = cut->eml_rba( key = 2 ). + + "Verifying the result + cl_abap_unit_assert=>assert_equals( + act = lines( t_read_res_ch ) + exp = 2 + msg = `The number of lines does not match` + quit = if_abap_unit_constant=>quit-no ). + + TYPES struc_read_ch TYPE STRUCTURE FOR READ RESULT zdemo_abap_rap_ro_m\_child. + cl_abap_unit_assert=>assert_equals( + act = t_read_res_ch[ 2 ] + exp = VALUE struc_read_ch( key_field = 2 key_ch = 23 field_ch1 = 'xxx' field_ch2 = 223 ) + msg = `The values do not match the expected result` + quit = if_abap_unit_constant=>quit-no ). + ENDMETHOD. + + + METHOD test_eml_modify_root. + "--- 4a) --- + "Method that demonstrates mocking an ABAP EML API (create request) + + "Data object delcarations used in the test method + DATA create_tab_ro TYPE TABLE FOR CREATE zdemo_abap_rap_ro_m. + DATA mapped_resp TYPE RESPONSE FOR MAPPED EARLY zdemo_abap_rap_ro_m. + DATA failed_resp TYPE RESPONSE FOR FAILED EARLY zdemo_abap_rap_ro_m. + DATA reported_resp TYPE RESPONSE FOR REPORTED EARLY zdemo_abap_rap_ro_m. + + "Preparing test data (RAP BO instances, response parameters) + create_tab_ro = VALUE #( ( %cid = `cid1` key_field = 3 field1 = 'aaa' field2 = 'bbb' field3 = 30 field4 = 300 ) + ( %cid = `cid2` key_field = 4 field1 = 'ccc' field2 = 'ddd' field3 = 40 field4 = 400 ) ). + + mapped_resp-root = VALUE #( ( %cid = 'cid1' key_field = 3 ) ( %cid = 'cid2' key_field = 4 ) ). + failed_resp-root = VALUE #( ). + reported_resp-root = VALUE #( ). + + "Configuring input and output for the ABAP EML create request + DATA(input_config_modify) = cl_botd_mockemlapi_bldrfactory=>get_input_config_builder( )->for_modify( ). + DATA(output_config_modify) = cl_botd_mockemlapi_bldrfactory=>get_output_config_builder( )->for_modify( ). + + "Creating input for the entities (here, only one entity is included) + DATA(eml_modify_input) = input_config_modify->build_entity_part( entity + )->set_instances_for_create( create_tab_ro ). + + "Input configuration + DATA(input) = input_config_modify->build_input_for_eml( )->add_entity_part( eml_modify_input ). + + "Output configuration + DATA(output) = output_config_modify->build_output_for_eml( )->set_mapped( mapped_resp + )->set_failed( failed_resp )->set_reported( reported_resp ). + + "Configuring the RAP BO test double + td_eml = eml_env->get_test_double( entity ). + td_eml->configure_call( )->for_modify( )->when_input( input )->then_set_output( output ). + + "Calling method (containing the ABAP EML read request) of the class under test + cut->eml_modify_root( + EXPORTING + instances = VALUE #( ( %cid = `cid1` key_field = 3 field1 = 'aaa' field2 = 'bbb' field3 = 30 field4 = 300 ) + ( %cid = `cid2` key_field = 4 field1 = 'ccc' field2 = 'ddd' field3 = 40 field4 = 400 ) ) + IMPORTING + mapped = DATA(m) + failed = DATA(f) + reported = DATA(r) + ). + + "Verifying the result + cl_abap_unit_assert=>assert_equals( + act = lines( m-root ) + exp = 2 + msg = `The number of lines does not match` + quit = if_abap_unit_constant=>quit-no ). + + cl_abap_unit_assert=>assert_equals( + act = lines( f-root ) + exp = 0 + msg = `The number of lines does not match` + quit = if_abap_unit_constant=>quit-no ). + + cl_abap_unit_assert=>assert_equals( + act = lines( r-root ) + exp = 0 + msg = `The number of lines does not match` + quit = if_abap_unit_constant=>quit-no ). + + ENDMETHOD. + + METHOD test_eml_read_root_buffer_td. + "--- 4b) --- + "Method that demonstrates transactional buffer test doubles + + "Creating test double + "Note: You have more configuration options. Check, for example, the + "methods configure_additional_behavior, set_fields_handler methods, etc. + "as described in the documentation. + DATA(double) = buffer_env->get_test_double( 'zdemo_abap_rap_ro_u' ). + + "Preparing test data + "Populating the transactional buffer with an ABAP EML create request + MODIFY ENTITIES OF zdemo_abap_rap_ro_u + ENTITY root + CREATE FIELDS ( key_field field1 field2 field3 field4 ) + WITH VALUE #( ( %cid = `cid5` key_field = 5 field1 = 'eee' ) + ( %cid = `cid6` key_field = 6 field1 = 'fff' ) ) + REPORTED DATA(reported) + FAILED DATA(failed) + MAPPED DATA(mapped). + + "Calling method (containing an ABAP EML read request) of the class under test + cut->eml_read_root_buffer_td( + EXPORTING + key = 5 + RECEIVING + tab_ro_u = DATA(read_result) ). + + "Verifying the result + cl_abap_unit_assert=>assert_equals( + act = read_result[ 1 ]-key_field + exp = 5 + msg = `The value does not match the expected result` + quit = if_abap_unit_constant=>quit-no ). + + cl_abap_unit_assert=>assert_equals( + act = read_result[ 1 ]-field1 + exp = 'eee' + msg = `The value does not match the expected result` + quit = if_abap_unit_constant=>quit-no ). + + "Another calling of the method of the class under test (non-existent instance + "in the transactional buffer test double) + cut->eml_read_root_buffer_td( + EXPORTING + key = 1 + RECEIVING + tab_ro_u = DATA(read_result_f) ). + + cl_abap_unit_assert=>assert_initial( + EXPORTING + act = read_result_f + msg = `An instance was found` + quit = if_abap_unit_constant=>quit-no ). + + ENDMETHOD. + + METHOD test_cds_standalone. + "--- 3) --- + "Method that tests the implementation logic of a CDS view entity + "Unlike the 'test_select_cds' method, this method does not call a method + "in the class under test. + + "Preparing test data + prepare_testdata_set_cds( ). + + "Retrieving data from the CDS view entity (test data is used here) + SELECT * FROM zdemo_abap_cds_ve_agg_exp INTO TABLE @DATA(result). + + "Verifying the result + cl_abap_unit_assert=>assert_equals( + act = result[ 1 ]-carrid + exp = 'XX' + msg = `The value does not match the expected result` + quit = if_abap_unit_constant=>quit-no ). + + cl_abap_unit_assert=>assert_equals( + act = result[ 1 ]-avg_seats_occ + exp = '411.75' + msg = `The value does not match the expected result` + quit = if_abap_unit_constant=>quit-no ). + + cl_abap_unit_assert=>assert_equals( + act = result[ 1 ]-max_occ_seats + exp = 458 + msg = `The value does not match the expected result` + quit = if_abap_unit_constant=>quit-no ). + ENDMETHOD. + +ENDCLASS. diff --git a/src/ztcl_demo_abap_unit_tdf_testcl.clas.xml b/src/ztcl_demo_abap_unit_tdf_testcl.clas.xml new file mode 100644 index 0000000..5b5f7eb --- /dev/null +++ b/src/ztcl_demo_abap_unit_tdf_testcl.clas.xml @@ -0,0 +1,17 @@ + + + + + + ZTCL_DEMO_ABAP_UNIT_TDF_TESTCL + E + ABAP cheat sheet: Creating Test Doubles Using ABAP Framework + 1 + X + X + X + X + + + +