diff --git a/01_Internal_Tables.md b/01_Internal_Tables.md index 30602bb..6b88307 100644 --- a/01_Internal_Tables.md +++ b/01_Internal_Tables.md @@ -11,7 +11,10 @@ - [Copying Internal Tables](#copying-internal-tables) - [Using INSERT and APPEND Statements to Populate Internal Tables](#using-insert-and-append-statements-to-populate-internal-tables) - [Creating and Filling Internal Tables Using Constructor Expressions](#creating-and-filling-internal-tables-using-constructor-expressions) - - [Excursion: Using Internal Tables in ABAP SQL Statements](#excursion-using-internal-tables-in-abap-sql-statements) + - [VALUE operator](#value-operator) + - [CORRESPONDING operator](#corresponding-operator) + - [FILTER Operator\*\*](#filter-operator) + - [NEW Operator](#new-operator) - [Reading Single Lines from Internal Tables](#reading-single-lines-from-internal-tables) - [Determining the Target Area when Reading Single Lines](#determining-the-target-area-when-reading-single-lines) - [Reading a Single Line by Index](#reading-a-single-line-by-index) @@ -21,6 +24,9 @@ - [Checking the Existence and the Index of a Line in an Internal Table](#checking-the-existence-and-the-index-of-a-line-in-an-internal-table) - [Processing Multiple Internal Table Lines Sequentially](#processing-multiple-internal-table-lines-sequentially) - [Iteration Expressions](#iteration-expressions) + - [Operations with Internal Tables Using ABAP SQL SELECT Statements](#operations-with-internal-tables-using-abap-sql-select-statements) + - [Internal Tables as Target Data Objects](#internal-tables-as-target-data-objects) + - [Querying from Internal Tables](#querying-from-internal-tables) - [Sorting Internal Tables](#sorting-internal-tables) - [Modifying Internal Table Content](#modifying-internal-table-content) - [Deleting Internal Table Content](#deleting-internal-table-content) @@ -537,7 +543,9 @@ INSERT LINES OF itab2 INTO itab INDEX i.

⬆️ back to top

### Creating and Filling Internal Tables Using Constructor Expressions +The constructor expressions can be specified in/with various positions/statements in ABAP. In most of the following snippets, simple assignments are demonstrated. +#### VALUE operator As mentioned above, table lines that are constructed inline as arguments to the `VALUE` operator, for example, can be added to internal tables. In the following cases, internal tables are populated @@ -598,6 +606,7 @@ itab = VALUE #( ( comp1 = a comp2 = b ...)                 ... ). ``` +#### CORRESPONDING operator **Copying the content of another internal table** using the [`CORRESPONDING`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenconstructor_expr_corresponding.htm) operator. @@ -652,14 +661,14 @@ itab = CORRESPONDING #( itab2 DISCARDING DUPLICATES ). ``` abap itab_nested2 = CORRESPONDING #( DEEP itab_nested1 ). -itab_nested2 = CORRESPONDING #( DEEP BASE ( itab_nested2 ) itab_nested1 ) +itab_nested2 = CORRESPONDING #( DEEP BASE ( itab_nested2 ) itab_nested1 ). MOVE-CORRESPONDING itab_nested1 TO itab_nested2 EXPANDING NESTED TABLES. MOVE-CORRESPONDING itab_nested1 TO itab_nested2 EXPANDING NESTED TABLES KEEPING TARGET LINES. ``` -**Using the `FILTER` operator** +#### FILTER Operator** To create an internal table by copying data from another internal table and filtering out lines that do not meet the `WHERE` condition, you can use the [`FILTER` @@ -735,107 +744,28 @@ DATA(f11) = FILTER #( itab2 USING KEY sec_key EXCEPT IN filter_tab2 WHERE num =

⬆️ back to top

-### Excursion: Using Internal Tables in ABAP SQL Statements -For more details on ABAP SQL, see the [ABAP SQL](03_ABAP_SQL.md) cheat sheet. +#### NEW Operator -**Adding multiple lines from a database table to an internal table** using -[`SELECT`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapselect.htm), -for example, based on a condition. In the case below, the internal table -is created inline. -``` abap -SELECT FROM dbtab - FIELDS comp1, comp2 ... - WHERE ... - INTO TABLE @DATA(itab_sel). -``` +Using the instance operator [`NEW`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenconstructor_expression_new.htm), you can create [anonymous data objects](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenanonymous_data_object_glosry.htm "Glossary Entry"), such as anonymous internal tables. You can access the lines, components or the entire data objects by [dereferencing](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abendereferencing_operat_glosry.htm). For more information, refer to the [Dynamic Programming](06_Dynamic_Programming.md) and [Constructor Expressions](05_Constructor_Expressions.md) cheat sheets. -**Sequentially adding multiple rows** from a database table to an internal table using `SELECT ... ENDSELECT.`, for example, based on a condition. In this case, the selected data is first stored in a structure that can be further processed and added to an internal table. +```abap +TYPES: BEGIN OF s, + a TYPE c LENGTH 3, + b TYPE i, + END OF s, + tab_type TYPE TABLE OF s WITH EMPTY KEY. -``` abap -SELECT FROM dbtab - FIELDS comp1, comp2 ... - WHERE ... - INTO @DATA(struc_sel). +"Creating and populating an anonymous data object +DATA(dref_tab) = NEW tab_type( ( a = 'aaa' b = 1 ) + ( a = 'bbb' b = 2 ) ). - IF sy-subrc = 0. - APPEND struc_sel TO itab. -  ... - ENDIF. -ENDSELECT. -``` - -Adding multiple lines from a database table using `SELECT`, for example, based on a condition when the database table has a line type that is incompatible with the internal table. The `*` character means that all fields are selected. The other examples define specific fields. -The `APPENDING CORRESPONDING FIELDS INTO TABLE` addition appends the selected data to the end of the table without deleting existing -table entries. The `INTO CORRESPONDING FIELDS OF TABLE` addition adds lines and deletes existing table entries. -``` abap -SELECT FROM dbtab2 - FIELDS * - WHERE ... - APPENDING CORRESPONDING FIELDS OF TABLE @itab. - -SELECT FROM dbtab2 - FIELDS * - WHERE ... - INTO CORRESPONDING FIELDS OF TABLE @itab. -``` -Adding multiple lines from an internal table to another internal table using `SELECT`. Note the alias name that must be defined for the -internal table. -``` abap -SELECT comp1, comp2, ... - FROM @itab2 AS it_alias - INTO TABLE @DATA(itab_sel). -``` - -**Combining data from multiple tables into one internal table** using an [inner -join](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abeninner_join_glosry.htm "Glossary Entry"). -The following example joins data of an internal and a database table -using a `SELECT` statement and the [`INNER JOIN`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapselect_join.htm) addition. Note that the field list includes fields from both tables. The fields are referred to using `~`. -``` abap -SELECT it_alias~comp1, it_alias~comp2, dbtab~comp3 ... - FROM @itab AS it_alias - INNER JOIN dbtab ON it_alias~comp1 = dbtab~comp1 - INTO TABLE @DATA(it_join_result). -``` - -Filling an internal table from a database table using -[subqueries](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abensubquery_glosry.htm "Glossary Entry"). -The following two examples fill an internal table from a database table. In the first example, a subquery is specified in the -`WHERE` clause with the `NOT IN` addition. It checks whether a value matches a value in a set of values -specified in parentheses. The second example fills an internal table depending on data in another table. A subquery with the `EXISTS` addition is specified in -the `WHERE` clause. In this -case, the result of the subquery, which is another -`SELECT` statement, is checked to see if an entry exists in -a table based on the specified conditions. - -``` abap -SELECT comp1, comp2, ... - FROM dbtab - WHERE comp1 NOT IN ( a, b, c ... ) - INTO TABLE @DATA(it_subquery_result1). - -SELECT comp1, comp2, ... - FROM dbtab - WHERE EXISTS ( SELECT 'X' FROM @itab AS itab_alias - WHERE comp1 = dbtab~comp1 ) - INTO TABLE @DATA(it_subquery_result2). -``` - -Filling internal table from a table based on the existence of data in -another table using the [`FOR ALL ENTRIES`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenwhere_all_entries.htm) addition. - -> **💡 Note**
-> Make sure that the internal table you are reading from is not initial. Therefore, it is recommended that you use a subquery as shown above: `... ( SELECT ... FROM @itab AS itab_alias WHERE ... ) ...`. - -``` abap -IF itab IS NOT INITIAL. - - SELECT dbtab~comp1, dbtab~comp2, ... - FROM dbtab - FOR ALL ENTRIES IN @itab - WHERE comp1 = @itab-comp1 - INTO TABLE @DATA(it_select_result). - -ENDIF. +"Access by derefencing +DATA(copy_deref_itab) = dref_tab->*. +DATA(read_line) = dref_tab->*[ 2 ]. +DATA(read_comp) = dref_tab->*[ 1 ]-a. +dref_tab->*[ 1 ]-a = 'zzz'. +ASSERT dref_tab->*[ 1 ]-a = 'zzz'. +INSERT VALUE s( a = 'yyy' b = 3 ) INTO TABLE dref_tab->*. ```

⬆️ back to top

@@ -871,7 +801,7 @@ The following code snippets include [`READ TABLE`](https://help.sap.com/doc/abap - Assigning a line to a [field symbol](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenfield_symbol_glosry.htm "Glossary Entry"), - for example, using an inline declaration (`ASSIGNING `). When you then access the field symbol, it means that you access the found table line. There is no actual copying of + for example, using an inline declaration (`... ASSIGNING FIELD-SYMBOL() ...`). When you then access the field symbol, it means that you access the found table line. There is no actual copying of content. Therefore, modifying the field symbol means modifying the table line directly. Note that you cannot use the `TRANSPORTING` addition since the entire table is @@ -1035,7 +965,7 @@ READ TABLE it INTO wa WITH KEY b = 2. When reading single lines in general, you can also address individual components of the line using the [component selector](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abencomponent_selector_glosry.htm "Glossary Entry") -`-` (or the [dereferencing +`-` (or the [object component selector](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenobject_component_select_glosry.htm) `->` or the [dereferencing operator](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abendereferencing_operat_glosry.htm "Glossary Entry") `->*` in the case of data reference variables). ``` abap @@ -1255,6 +1185,178 @@ The expressions are covered in the cheat sheet [Constructor Expressions](05_Cons

⬆️ back to top

+## Operations with Internal Tables Using ABAP SQL SELECT Statements + +- In ABAP, database data is buffered in a [table buffer](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abentable_buffer_glosry.htm) (internally, this happens in internal tables in the [shared memory](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenshared_memory_glosry.htm) of the ABAP server). +- During read access, it is checked if the data is in the buffer, and if so, a read happens directly from there. If not, the data is first loaded into the buffer. +- The [ABAP SQL engine](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenabap_sql_engine_glosry.htm) is involved in the read process. It processes reads and is used when tabular data is read (with a `SELECT` statement). This includes both buffered data from database tables in the table buffer and also internal tables of the current internal session. +- Which means ABAP SQL is executed in this buffer on the ABAP server, not directly on the database. + +So, ABAP SQL `SELECT` statements can be used for multiple purposes also on internal tables. The following snippets cover a selection. Find more details in the ABAP Keyword Documentation and in the [ABAP SQL cheat sheet](03_ABAP_SQL.md). + +> **💡 Note**
+> - No deep components (nested tables, strings) are allowed. +> - Trailing blanks of short text fields are truncated. +> - When joining multiple internal tables, it must be ensured that the ABAP SQL engine can handle it, which means only internal tables are used (no buffered database tables), no special features like hierarchies, aggregates, subselects, etc. are used. + +

⬆️ back to top

+ +### Internal Tables as Target Data Objects + +Adding multiple lines from a database table to an internal table using +[`SELECT`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapselect.htm), +for example, based on a condition. In the case below, the internal table +is created inline. +``` abap +SELECT FROM dbtab + FIELDS comp1, comp2 ... + WHERE ... + INTO TABLE @DATA(itab_sel). +``` + +Adding multiple lines from a database table using `SELECT`, for example, based on a condition when the database table has a line type that is incompatible with the internal table. The `*` character means that all fields are selected. The other examples define specific fields. +The `APPENDING CORRESPONDING FIELDS INTO TABLE` addition appends the selected data to the end of the table without deleting existing +table entries. The `INTO CORRESPONDING FIELDS OF TABLE` addition adds lines and deletes existing table entries. +``` abap +SELECT FROM dbtab2 + FIELDS * + WHERE ... + APPENDING CORRESPONDING FIELDS OF TABLE @itab. + +SELECT FROM dbtab2 + FIELDS * + WHERE ... + INTO CORRESPONDING FIELDS OF TABLE @itab. +``` + +Combining data from multiple database tables into one internal table using an [inner +join](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abeninner_join_glosry.htm "Glossary Entry"). +The following example uses the [`INNER JOIN`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapselect_join.htm) addition. Note that the field list includes fields from both tables. The fields are referred to using `~`. +``` abap +SELECT db1~comp1, db1~comp2, db2~comp_abc, db2~comp_xyz ... + FROM db1 + INNER JOIN db2 ON db1~comp1 = db2~comp1 + INTO TABLE @DATA(it_join_result). +``` + +Filling an internal table from a database table using +[subqueries](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abensubquery_glosry.htm "Glossary Entry"). +The following two examples fill an internal table from a database table. In the first example, a subquery is specified in the +`WHERE` clause with the `NOT IN` addition. It checks whether a value matches a value in a set of values +specified in parentheses. The second example fills an internal table depending on data in another table. A subquery with the `EXISTS` addition is specified in +the `WHERE` clause. In this +case, the result of the subquery, which is another +`SELECT` statement, is checked to see if an entry exists in +a table based on the specified conditions. + +``` abap +SELECT comp1, comp2, ... + FROM dbtab + WHERE comp1 NOT IN ( a, b, c ... ) + INTO TABLE @DATA(it_subquery_result1). + +SELECT comp1, comp2, ... + FROM db1 + WHERE EXISTS ( SELECT 'X' FROM db2 + WHERE comp1 = db1~comp1 ) + INTO TABLE @DATA(it_subquery_result2). +``` + +Populating an internal table from a table based on the existence of data in +another table using the [`FOR ALL ENTRIES`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenwhere_all_entries.htm) addition. + +> **💡 Note**
+> Make sure that the internal table you are reading from is not initial. Therefore, it is recommended that you use a subquery as shown above: `... ( SELECT ... FROM ... WHERE ... ) ...`. + +``` abap +IF itab IS NOT INITIAL. + + SELECT dbtab~comp1, dbtab~comp2, ... + FROM dbtab + FOR ALL ENTRIES IN @itab + WHERE comp1 = @itab-comp1 + INTO TABLE @DATA(it_select_result). + +ENDIF. +``` + +

⬆️ back to top

+ +### Querying from Internal Tables +In `SELECT` statements, internal tables can also be used as data sources. +The snippet shows adding multiple lines from an internal table to another internal table using `SELECT`. Note the alias name that must be defined for the internal table. + +``` abap +SELECT comp1, comp2, ... + FROM @itab AS it_alias + INTO TABLE @DATA(itab_sel). +``` + +Using the `LIKE` addition in the `WHERE` clause to extract internal table entries matching a specific pattern. +```abap +TYPES: BEGIN OF s, + a TYPE c LENGTH 3, + b TYPE i, + END OF s, + tab_type TYPE TABLE OF s WITH EMPTY KEY. +DATA(itab) = VALUE tab_type( ( a = 'abc' b = 1 ) ( a = 'zbc' b = 2 ) + ( a = 'bde' b = 3 ) ( a = 'yde' b = 4 ) ). + +SELECT a, b + FROM @itab AS it_alias + WHERE a LIKE '%bc' + INTO TABLE @DATA(itab_sel_like). + +*A B +*abc 1 +*zbc 2 +``` + +Using the `IN` addition in the `WHERE` clause to extract internal table entries based on values specified in an operand list. +```abap +SELECT a, b + FROM @itab AS it_alias + WHERE a IN ('bde', 'yde', 'zde') + INTO TABLE @DATA(itab_in). + +*A B +*bde 3 +*yde 4 +``` + +Combining data from multiple internal tables into one internal table using an [inner +join](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abeninner_join_glosry.htm "Glossary Entry"). See above. + +```abap +TYPES: BEGIN OF s, + a TYPE c LENGTH 3, + b TYPE c LENGTH 3, + c TYPE i, + END OF s, + tab_type TYPE TABLE OF s WITH EMPTY KEY. + +DATA(it1) = VALUE tab_type( ( a = 'aaa' b = 'bbb' c = 1 ) + ( a = 'ccc' b = 'ddd' c = 1 ) + ( a = 'eee' b = 'fff' c = 2 ) ). + +DATA(it2) = VALUE tab_type( ( a = 'ggg' b = 'hhh' c = 1 ) + ( a = 'iii' b = 'jjj' c = 1 ) + ( a = 'kkk' b = 'lll' c = 3 ) ). + +SELECT it_alias1~a, it_alias2~b + FROM @it1 AS it_alias1 + INNER JOIN @it2 AS it_alias2 ON it_alias1~c = it_alias2~c + INTO TABLE @DATA(it_join_result). + +*A B +*aaa hhh +*aaa jjj +*ccc hhh +*ccc jjj +``` + +

⬆️ back to top

+ ## Sorting Internal Tables - Sorted tables are stored in the memory in an automatically sorted diff --git a/02_Structures.md b/02_Structures.md index 3ed13a2..ad3baf3 100644 --- a/02_Structures.md +++ b/02_Structures.md @@ -57,7 +57,7 @@ TYPES: BEGIN OF struc_type,        END OF struc_type. ``` -Alternatively, you can also use the following syntax. However, a chained statement provides better readability. +Alternatively, you can also use the following syntax. However, a chained statement may provide better readability. ``` abap TYPES BEGIN OF struc_type. TYPES comp1 TYPE ... . @@ -98,7 +98,8 @@ TYPES: BEGIN OF struc_type, **Creating structures** -- To create a structure in an ABAP program, you can use the `DATA` keyword. - It works in the same way as the `TYPES` statement above. +- To create a structure in an ABAP program, you can use the `DATA` keyword. +- It works in the same way as the `TYPES` statement above. - Unlike the `TYPES` statement, you can use the [`VALUE`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapdata_options.htm) addition to set default values. ``` abap @@ -113,7 +114,7 @@ DATA: BEGIN OF struc, END OF struc. ``` -Alternatively, you can use the following syntax. Similar to above, a chained statement provides better readability. +Alternatively, you can use the following syntax. Similar to above, a chained statement may provide better readability. ``` abap DATA BEGIN OF struc. @@ -177,7 +178,7 @@ FINAL(struc_11) = struc_9. "value assignments) ... DATA(struc_a) = VALUE struc_type( ). -"... yields the same result as the following declaration. +"... is similar to the following declaration. DATA struc_b TYPE struc_type. "Structures declared inline instead of an extra declared variable @@ -678,7 +679,7 @@ ENDLOOP. **Inserting a single row into a database table from a structure** using ABAP SQL statements with [`INSERT`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapinsert_dbtab.htm). The following statements can be considered as alternatives. The third statement shows that instead of inserting a row from an existing structure, you can create and fill a structure directly. -Note that you should avoid inserting a row with a particular key into the database table if a row with the same key already exists. +Note that you should avoid inserting a row with a particular key into the database table if a row with the same key already exists. Note that with this and the followig syntax, various options/expressions are possible. ``` abap INSERT INTO dbtab VALUES @struc. diff --git a/05_Constructor_Expressions.md b/05_Constructor_Expressions.md index f8bbd68..88cd26e 100644 --- a/05_Constructor_Expressions.md +++ b/05_Constructor_Expressions.md @@ -6,7 +6,6 @@ - [Introduction](#introduction) - [VALUE](#value) - [CORRESPONDING](#corresponding) - - [NEW](#new) - [CONV](#conv) - [EXACT](#exact) - [REF](#ref) @@ -425,7 +424,7 @@ topic](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file | Addition | Details | |---|---| | `BASE` | Keeps original values. Unlike, for example, the operator `VALUE`, a pair of parentheses must be set around `BASE`. | -| `MAPPING` | Enables the mapping of component names, i. e. a component of a source structure or source table can be assigned to a differently named component of a target structure or target table (e. g. `MAPPING c1 = c2`). | +| `MAPPING` | Enables the mapping of component names, i. e. a component of a source structure or source table can be assigned to a differently named component of a target structure or target table (e. g. `MAPPING c1 = c2`). The `DEFAULT` addition is possible when using the `MAPPING` addition. It allows the assignment of values for a target component based on an expression. | | `EXCEPT` | You can specify components that should not be assigned content in the target data object. They remain initial. In doing so, you exclude identically named components in the source and target object that are not compatible or convertible from the assignment to avoid syntax errors or runtime errors. | | `DISCARDING DUPLICATES` | Relevant for tabular components. Handles duplicate lines and prevents exceptions when dealing with internal tables that have a unique primary or secondary table key. | | `DEEP` | Relevant for deep tabular components. They are resolved at every hierarchy level and identically named components are assigned line by line. | @@ -575,6 +574,89 @@ two statements are not the same: >MOVE-CORRESPONDING struc1 TO struc2. >``` +`DEFAULT` addition when using `MAPPING`: +- This addition allows the assignment of values for a target component based on an expression (which is evaluated before the `CORRESPONDING` expression). +- `DEFAULT` can be preceded by the source component. In this case, the source component's value is assigned to the left-hand side only if the source component is not initial. If it is initial, the value of the expression following the `DEFAULT` addition is assigned. + +´´´abap +"Creating and populating data objects to work with +DATA: BEGIN OF struc1, + id1 TYPE i, + a TYPE string, + b TYPE string, + c TYPE i, + d TYPE string, + e TYPE i, + END OF struc1. + +DATA: BEGIN OF struc2, + id2 TYPE i, + a TYPE string, + b TYPE string, + c TYPE i, + d TYPE string, + z TYPE i, + END OF struc2. + +DATA itab1 LIKE TABLE OF struc1 WITH EMPTY KEY. + +"Populating structure +struc1 = VALUE #( id1 = 1 a = `a` b = `b` c = 2 d = `d` e = 3 ). + +"--------- Assignment using CORRESPONDING (DEFAULT only) --------- +"- Component a: It is not specified but it is mapped anyway +" due to the identical name and not being explicitly specified +" for mapping +"- The other components demonstrate various expressions +" in combination with the DEFAULT addition. +"- Component d: The internal table is initial in the example. The +" DEFAULT addition to the VALUE operator avoids a runtime error +" and specifies a default value in case of a non-existent table +" line. +struc2 = CORRESPONDING #( + struc1 MAPPING id2 = id1 + b = DEFAULT `ha` && `llo` + c = DEFAULT 1 + 5 + d = DEFAULT VALUE #( itab1[ 1 ]-d DEFAULT `hi` ) + z = DEFAULT cl_abap_random_int=>create( + seed = cl_abap_random=>seed( ) + min = 1 + max = 100 )->get_next( ) ). + +*struc2 (the value of z may vary because a random integer is created): +*ID2 A B C D Z +*1 a hallo 6 hi 28 + +"--- Assignment using CORRESPONDING (DEFAULT preceded by the source --- +"--- component on the right-hand side) -------------------------------- +"- The example is similar to above. Various expressions are included +" in combination with the DEFAULT addition +"- Component a: It is not specified; see above. +"- Components b/e: The values of the components b and e in the source +" are initial. Therefore, the result of the expression is assigned. +"- Components c/d: The values of the components c and d in the source +" are not initial. Therefore, the values of c and d are assigned. + +"Populating structure +struc1 = VALUE #( id1 = 1 a = `a` b = `` c = 2 d = `d` e = 0 ). + +struc2 = CORRESPONDING #( + struc1 MAPPING id2 = id1 + b = b DEFAULT `ha` && `llo` + c = c DEFAULT 1 + 5 + d = d DEFAULT VALUE #( itab1[ 1 ]-d DEFAULT `hi` ) + z = e DEFAULT cl_abap_random_int=>create( + seed = cl_abap_random=>seed( ) + min = 1 + max = 100 )->get_next( ) ). + +*struc2 (the value of z may vary because a random integer is created): +*ID2 A B C D Z +*1 a hallo 2 d 30 +``` + + +

⬆️ back to top

## NEW diff --git a/06_Dynamic_Programming.md b/06_Dynamic_Programming.md index 47f1489..ed9bdd7 100644 --- a/06_Dynamic_Programming.md +++ b/06_Dynamic_Programming.md @@ -1827,6 +1827,82 @@ CALL METHOD oref2->('TRIPLE') PARAMETER-TABLE ptab. result = ptab[ name = 'R_TRIPLE' ]-('VALUE')->*. "9 ``` +**Excursion** + +The following simplified example highlights several things in the context of a dynamic invoke example: +- Dynamic invoke and assigning actual parameters to formal parameters statically +- Creating instances of classes dynamically, using generic types +- The concepts of static vs. dynamic type, upcast vs. downcast +- Dynamic ABAP does the same as static ABAP, but with dynamic ABAP, errors may not be discovered until runtime. +- Type compliance (see the [General Rules for Typing](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abentyping_check_general.htm)) + +```abap +CLASS zcl_demo_test DEFINITION + PUBLIC + FINAL + CREATE PUBLIC . + + PUBLIC SECTION. + INTERFACES if_oo_adt_classrun. + METHODS some_method IMPORTING obj TYPE REF TO zcl_demo_abap_objects. + PROTECTED SECTION. + PRIVATE SECTION. +ENDCLASS. + +CLASS zcl_demo_test IMPLEMENTATION. + METHOD if_oo_adt_classrun~main. + + "Creating an instance of a class dynamically + "Here, an object reference variable of the generic type 'object' + "is used. This generic type is the static type. + "After the CREATE OBJECT statement in the example, the dynamic type + "is 'ref to zcl_demo_abap_objects'. It is the type which the variable + "points to at runtime. + DATA oref TYPE REF TO object. + CREATE OBJECT oref TYPE ('ZCL_DEMO_ABAP_OBJECTS'). + + "In the example, the some_method method expects an object reference + "variable with type 'ref to zcl_demo_abap_objects'. + "A static method call such as the following is not possible. The compiler will + "raise an error since there is no type compliance. This is because the + "static type of the formal parameter is not compliant with the static + "type of oref - even if the dynamic type to which the variable points + "to at runtime is suitable. + "some_method( oref ). + + "As a rule, static ABAP does the same as dynamic ABAP. So, the following + "dynamic statement raises an error at runtime. There is no compiler + "error shown at compile time. + TRY. + CALL METHOD ('SOME_METHOD') EXPORTING obj = oref. + CATCH cx_sy_dyn_call_illegal_type. + ENDTRY. + + "See also the following statement. No error at compile time + "with the nonsense formal parameter. + "It is checked at runtime and will consequently raise an issue. + TRY. + CALL METHOD ('SOME_METHOD') EXPORTING abcdef = oref. + CATCH cx_sy_dyn_call_param_missing. + ENDTRY. + + "If you have such a use case, and deal with generic/dynamic types, note + "the general rules for typing in the ABAP Keyword Documentation. + "A prior downcast (i.e. from the more generic type 'object' to the less + "specific type zcl_demo_abap_objects) can be done. In this context, note + "that upcasts are possible (and implicitly done) when assigning the parameters, + "but downcasts must always be done explicitly, for example, using the CAST + "operator as follows (if you know the type to cast to). + CALL METHOD ('SOME_METHOD') EXPORTING obj = CAST zcl_demo_abap_objects( oref ). + ENDMETHOD. + + METHOD some_method. + ... + ENDMETHOD. + +ENDCLASS. +``` +

⬆️ back to top

### Dynamic Formatting Option Specifications in String Templates diff --git a/07_String_Processing.md b/07_String_Processing.md index c98b65f..006dc1b 100644 --- a/07_String_Processing.md +++ b/07_String_Processing.md @@ -1670,6 +1670,13 @@ Character Sets, Ranges, Subgroups and Lookarounds > "Regular expression: any character or a new line with zero or more repretitions > REPLACE ALL OCCURRENCES OF PCRE `(

)(.|\n)*?(<\/p>)` IN str_b WITH `$1Hi$3`. > "

Hi

Hi

Hi

+> ``` +> - Regarding special characters, check the [Special Characters](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenregex_pcre_syntax_specials.htm) topic in the ABAP Keyword Documentation. For example, a non-breaking space whose hex code is *U+00A0*. You can replace all of the non-breaking space occurrences in a string as follows: +> ```abap +> REPLACE ALL OCCURRENCES OF PCRE `\x{00A0}` IN some_string WITH ``. +> "Alternative +> REPLACE ALL OCCURRENCES OF PCRE `(*UTF)\N{U+00A0}` IN some_string WITH ``. +> ``` Anchors and Positions diff --git a/08_EML_ABAP_for_RAP.md b/08_EML_ABAP_for_RAP.md index c2a6efb..fd235df 100644 --- a/08_EML_ABAP_for_RAP.md +++ b/08_EML_ABAP_for_RAP.md @@ -167,7 +167,7 @@ The following points cover RAP-related terms such as *RAP business objects* and further down. - Usually, saver classes are not needed in managed RAP BOs (except for special variants of managed RAP BOs which are not covered - here). Local handler classes are, as mentioned above, usually + here). Local handler classes are usually needed in managed RAP BOs if implementations are required that go beyond standard operations. - Note: In more complex scenarios, with RAP BOs that @@ -200,7 +200,7 @@ focus on). ## Excursion: RAP Behavior Definition (BDEF) -- As mentioned in the RAP terms and as the name implies, a RAP behavior definition describes a RAP business object (RAP BO) by defining its behavior for all of its RAP BO entities. +- As the name implies, a RAP behavior definition describes a RAP business object (RAP BO) by defining its behavior for all of its RAP BO entities. - BDL source code is used in a BDEF. - Once you have created ... - the CDS root entity of a RAP BO, ADT helps you create the skeleton of a BDEF (e.g., right-click on the CDS root entity and choose *New Behavior Definition* from the pop-up). @@ -443,7 +443,7 @@ authorization dependent by _parent ## ABAP Behavior Pools (ABP) -As mentioned above, you can access RAP BO data from inside AS ABAP using +You can access RAP BO data from inside AS ABAP using EML. Among other things, EML allows you to read or modify RAP BOs by accessing the RAP BO data (the RAP BO instances) in the transactional buffer and trigger the persistent storage or reset changes. More @@ -738,7 +738,7 @@ TYPES der_typ TYPE TABLE FOR DELETE entity. "Response parameters DATA map TYPE RESPONSE FOR MAPPED entity. DATA fail TYPE RESPONSE FOR FAILED entity. -DATA rep TYPE RESPONSE FOR REPORTED entity. +DATA resp TYPE RESPONSE FOR REPORTED entity. ``` > **💡 Note**
> Some of the derived types can only be created and accessed in implementation classes. @@ -857,16 +857,16 @@ Bullet points on selected `%` components: DATA itab_cr TYPE TABLE FOR CREATE zdemo_abap_rap_ro_m. itab_cr = VALUE #( %control-key_field = if_abap_behv=>mk-on - %control-field1 = if_abap_behv=>mk-on - %control-field2 = if_abap_behv=>mk-on - %control-field3 = if_abap_behv=>mk-on - %control-field4 = if_abap_behv=>mk-on - ( %cid = `cid1` - %key-key_field = 1 - field1 = 'aaa' - field2 = 'bbb' - field3 = 11 - field4 = 111 ) ). + %control-field1 = if_abap_behv=>mk-on + %control-field2 = if_abap_behv=>mk-on + %control-field3 = if_abap_behv=>mk-on + %control-field4 = if_abap_behv=>mk-on + ( %cid = `cid1` + %key-key_field = 1 + field1 = 'aaa' + field2 = 'bbb' + field3 = 11 + field4 = 111 ) ). "Secondary table keys available in the example table: cid, entity @@ -1009,10 +1009,10 @@ cr_der_type = VALUE #( key_field = 1 UPDATE zdemo_abap_rapt1 FROM @cr_der_type INDICATORS SET STRUCTURE %control MAPPING FROM ENTITY. -"--------------- DELETE --------------- *KEY_FIELD FIELD1 FIELD2 FIELD3 FIELD4 *1 ### ZZZ 2 200 +"--------------- DELETE --------------- DELETE zdemo_abap_rapt1 FROM @cr_der_type MAPPING FROM ENTITY. ``` @@ -1190,7 +1190,7 @@ Excursion: Specifying `%control` component values in the short form of `VALUE` c "The following EML statement creates RAP BO instances. The BDEF derived "type is created inline. With the FROM addition, the %control values "must be specified explicitly. You can provide the corresponding values -"for all table lines using the short form, i.e. outiside of the inner +"for all table lines using the short form, i.e. outside of the inner "parentheses, instead of individually specifying the values for each "instance within the parentheses. In this case, the corresponding %control "component value is assigned for all of the following table lines. @@ -1455,7 +1455,7 @@ READ ENTITIES OPERATIONS op_tab. ENTITIES`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapcommit_entities.htm) statement triggers the RAP save sequence. Without such a statement, the modified RAP BO instances that are available in the - transactional buffer are not persisted to the database. As mentioned above, in case of a natively supported RAP + transactional buffer are not persisted to the database. In case of a natively supported RAP scenario (for example, when using OData), the `COMMIT ENTITIES` request is executed automatically. - `COMMIT ENTITIES` implicitly includes [`COMMIT @@ -1670,7 +1670,7 @@ instances](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm? for the current ABAP EML request within the [RAP interaction phase](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenrap_int_phase_glosry.htm "Glossary Entry") in one RAP transaction. - **Note:** Specify `%cid` even if there are no further operations referring to it. - Special case: Late numbering - - As mentioned above, in late numbering scenarios newly created + - In late numbering scenarios newly created entity instances are given their final key only shortly before saving in the database, i. e. you deal with preliminary keys in the RAP interaction phase and the early phase of the [RAP save sequence](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenrap_save_seq_glosry.htm "Glossary Entry"). @@ -1839,7 +1839,7 @@ Saver methods called in the RAP late save phase: [`cleanup`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abensaver_method_cleanup.htm) method: After a successful save, the [`cleanup`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abensaver_method_cleanup.htm) method clears the transactional buffer. It completes the save sequence. **Commit and Rollback in a RAP Transaction** -The default ABAP statements for RAP are `COMMIT ENTITIES` (triggers the RAP save sequence and the final database commit; as mentioned above, in natively supported RAP scenarios, the commit is performed implicitly and automatically by the RAP runtime engine) and `ROLLBACK ENTITIES` (rolls back all changes of the current RAP transaction, i.e. the transactional buffer is cleared by calling the `cleanup` method). Both are RAP-specific and end the RAP transaction. +The default ABAP statements for RAP are `COMMIT ENTITIES` (triggers the RAP save sequence and the final database commit; in natively supported RAP scenarios, the commit is performed implicitly and automatically by the RAP runtime engine) and `ROLLBACK ENTITIES` (rolls back all changes of the current RAP transaction, i.e. the transactional buffer is cleared by calling the `cleanup` method). Both are RAP-specific and end the RAP transaction. *Notes on `COMMIT ...` and `ROLLBACK ...` statements due to the integration of RAP transactions into the SAP LUW:* diff --git a/14_ABAP_Unit_Tests.md b/14_ABAP_Unit_Tests.md index a66e7eb..d83cc0e 100644 --- a/14_ABAP_Unit_Tests.md +++ b/14_ABAP_Unit_Tests.md @@ -1,524 +1,524 @@ - - -# ABAP Unit Tests - -- [ABAP Unit Tests](#abap-unit-tests) - - [Unit Tests in ABAP](#unit-tests-in-abap) - - [High-Level Steps for ABAP Unit Tests](#high-level-steps-for-abap-unit-tests) - - [Creating Test Classes](#creating-test-classes) - - [Creating and Implementing Test Methods](#creating-and-implementing-test-methods) - - [Special Methods for Implementing the Test Fixture](#special-methods-for-implementing-the-test-fixture) - - [Handling Dependencies](#handling-dependencies) - - [Test Seams](#test-seams) - - [Running and Evaluating ABAP Unit Tests](#running-and-evaluating-abap-unit-tests) - - [More Information](#more-information) - - [Executable Example](#executable-example) - - -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. - -> **💡 Note**
-> - This cheat sheet focuses on testing methods. -> - See the [More Information](#more-information) section for links to more in-depth information. -> - The executable examples are **not** suitable role models for ABAP unit tests. They are intended to give you a rough idea. You should always work out your own solution for each individual case. - -## Unit Tests in ABAP -- Unit tests ... - - ensure the functional correctness of individual software units (i.e. a unit of code whose execution has a verifiable effect). - - are designed to test that the individual components of a larger software unit work correctly during the development and quality assurance phases. Typically, such individual software units are methods (the focus of this cheat sheet). - - must be created and run by developers. -- In ABAP, developers have [ABAP Unit](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenabap_unit_glosry.htm) - a test tool integrated into the ABAP runtime framework - at their disposal. It can be used to run individual or mass tests, and to evaluate test results. Note that comprehensive test runs can be performed using the [ABAP Test Cockpit](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenabap_test_cockpit_glosry.htm) -- In ABAP programs, individual unit tests are implemented as [test methods](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abentest_method_glosry.htm) of local [test classes](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abentest_class_glosry.htm). - -

⬆️ back to top

- -## High-Level Steps for ABAP Unit Tests - -- Identify dependend-on components (DOC) in your production code (i.e. your class/method) that need to be tested, and prepare the code for testing. - - Examples of DOCs: In your production code, a method is called that is outside of your code. Or, for example, whenever there is an interaction with the database. - - This means that there are dependencies that need to be taken into account for a unit test. These dependencies should be isolated and replaced by a test double. -- Create/Implement test doubles - - You can create test doubles manually. For example, you hardcode the test data to be used when the test is executed. Note: Automatic creation is not covered here. - - Ideally, the testability of your code has been prepared by providing interfaces to the DOC. Interfaces facilitate the testability because you can simply implement the interface methods. -- Inject the test doubles to ensure that the test data is used during the test run. -- Create test classes and methods -- Run unit tests - -> **💡 Note**
-> Of course, if there are no dependend-on components in your production code, you can skip the considerations of dependency isolation, test doubles and their injection into the production code. - -

⬆️ back to top

- -## Creating Test Classes - -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 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. -- can contain test methods, the special methods for the [fixture](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenfixture_glosry.htm), and other components. - - It is recommended that all components required for ABAP unit tests are defined in test classes only (so that they cannot be generated in production systems and cannot be addressed by production code). The components also include test doubles and other helper classes that do not contain test methods. - - -The skeleton of a test class might look like this: -``` abap -"Test class in the test include -CLASS ltc_test_class DEFINITION - FOR TESTING "Defines a class to be used in ABAP Unit - RISK LEVEL HARMLESS "Defines risk level, options: HARMLESS/CRITICAL/DANGEROUS - DURATION SHORT. "Expected test execution time, options: SHORT/MEDIUM/LONG - - ... - -ENDCLASS. - -CLASS ltc_test_class IMPLEMENTATION. - ... -ENDCLASS. -``` - -> **💡 Note**
-> - `FOR TESTING` can be used for multiple purposes: -> - Creating a test class containing test methods -> - Creating a test double -> - Creating helper methods to support ABAP unit tests -> - Note the possible [syntax options](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapclass_options.htm) before `FOR TESTING` -> - Optional addition `RISK LEVEL ...`: -> - `CRITICAL`: test changes system settings or customizing data (default) -> - `DANGEROUS`: test changes persistent data -> - `HARMLESS`: test does not change system settings or persistent data -> - Optional addition `DURATION ...`: -> - `SHORT`: execution time of only a few seconds is expected -> - `MEDIUM`: execution time of about one minute is expected -> - `LONG`: execution time of more than one minute is expected -> - To create a class in ADT, type "test" in the "Test Classes" tab and choose `CTRL + SPACE` to display the templates suggestions. You can then choose "testClass – Test class (ABAP Unit)". The skeleton of a test class is automatically generated. - -To test protected or private methods, you must declare [friendship](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenfriend_glosry.htm) with the class to be tested (class under test). -Example: - -``` abap -"The code in this snippet refers to the test class in the test include. -"Test class, declaration part -CLASS ltc_test_class DEFINITION - FOR TESTING - RISK LEVEL HARMLESS - DURATION SHORT. - - ... - -ENDCLASS. - -"Declaring friendship -CLASS cl_class_under_test DEFINITION LOCAL FRIENDS ltc_test_class. - -"Test class, implementation part -CLASS ltc_test_class IMPLEMENTATION. - ... -ENDCLASS. -``` - -If you have multiple test classes in the test include, you can place the friendship declaration for all classes at the top, for example, as follows. Note the [`DEFERRED`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapclass_deferred.htm) addition. - -``` abap -"Test include - -CLASS ltc_test_class_1 DEFINITION DEFERRED. -CLASS ltc_test_class_2 DEFINITION DEFERRED. -CLASS cl_class_under_test DEFINITION LOCAL FRIENDS ltc_test_class_1 - ltc_test_class_2. - -CLASS ltc_test_class_1 DEFINITION - FOR TESTING - RISK LEVEL HARMLESS - DURATION SHORT. - ... -ENDCLASS. - -CLASS ltc_test_class_1 IMPLEMENTATION. - ... -ENDCLASS. - -CLASS ltc_test_class_2 DEFINITION - FOR TESTING - RISK LEVEL HARMLESS - DURATION SHORT. - ... -ENDCLASS. - -CLASS ltc_test_class_2 IMPLEMENTATION. - ... -ENDCLASS. -``` - - -**Including Interfaces** -- Usually, all non-optional interface methods must be implemented. -- When you use the `PARTIALLY IMPLEMENTED` addition in test classes, you are not forced to implement all of the methods. -- It is particularly useful for interfaces to implement test doubles, and not all methods are necessary. - -Example of creating a test double: - -``` abap -"Test double class in a test include -CLASS ltd_test_double DEFINITION FOR TESTING. - PUBLIC SECTION. - INTERFACES some_intf PARTIALLY IMPLEMENTED. -ENDCLASS. - -CLASS ltd_test_double IMPLEMENTATION. - METHOD some_intf~some_meth. - ... - ENDMETHOD. -ENDCLASS. -``` - -

⬆️ back to top

- -## Creating and Implementing Test Methods - -Test methods ... -- are special [instance methods](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abeninstance_method_glosry.htm) of a test class in which a test is implemented. - - As with test classes, the `FOR TESTING` addition also applies to the test method declaration. - - Note that there are other syntax options, such as [`ABSTRACT` and others](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapmethods_testing.htm). -- are called by the ABAP Unit framework during a test run. -- are used to call units of production code and to check the result. - - The results are checked using methods of the class `CL_ABAP_UNIT_ASSERT`. -- should be private or protected if the methods are inherited. -- are called in an undefined order. -- have no parameters. - -**Creating Test Methods** - -Example: -``` abap -"Test class in a test include -"Test class declaration part -CLASS ltc_test_class DEFINITION - FOR TESTING - RISK LEVEL HARMLESS - DURATION SHORT. - - PRIVATE SECTION. - "Note: As a further component in test classes, usually, a reference variable - "is created for an instance of the class under test. - DATA ref_cut TYPE REF TO cl_class_under_test. - - "Test method declaration - METHODS some_test_method FOR TESTING. - -ENDCLASS. - -"Test class implementation part -CLASS ltc_test_class IMPLEMENTATION. - - METHOD some_test_method. - ... "Here goes the implementation of the test. - ENDMETHOD. - -ENDCLASS. -``` - -**Implementing Test Methods** -- The implementation ideally follows the *given-when-then* pattern. - - *given*: Preparing the test, e.g. creating an instance of the class under test and a local test double (to inject the test double into the class under test) - - *when*: Calling the procedure to be tested - - *then*: Checking and evaluating the test result using the static methods of the `CL_ABAP_UNIT_ASSERT` class -- The following points cover a selection of static methods of the class `CL_ABAP_UNIT_ASSERT` that can be used for the checks: - - `ASSERT_EQUALS`: Checks whether two data objects are the same. - - `ASSERT_BOUND`: Checks whether a reference variable is bound. - - `ASSERT_NOT_BOUND`: Negation of the one above. - - `ASSERT_INITIAL`: Checks whether a data object has its initial value. - - `ASSERT_NOT_INITIAL`: Negation of the one above. - - `ASSERT_SUBRC`: Checks the value of `sy-subrc`. - - `FAIL`: Triggers an error. -- For the class and methods, as well as the paramters, check the F2 information in the ADT. Depending on the method used, parameters can (or must) be specified. To name a few: - - `ACT`: of type `ANY` (in most cases), non-optional importing parameter of the methods, specifies a data object that is to be verified - - `EXP`: of type `ANY` (in most cases), a data object holding the expected value (non-optional for the `ASSERT_EQUALS` method) - - `MSG`: of type `CSEQUENCE`, error description - - `QUIT`: of type `INT1`, the specification affects the unit test flow control in case of an error, e.g. the constant `if_abap_unit_constant=>quit-no` can be used to determine that the unit test should not be terminated in case of an error - -The following code snippet shows a class declaration and implementation part in the production code. -A test method is to be created for the method. - -``` abap -"The code in this snippet refers to the production code in the global class. -"It shows a method declaration for a simple calculation in the class under test. - -"Class declaration part -CLASS cl_class_under_test DEFINITION - PUBLIC - CREATE PUBLIC. - - PRIVATE SECTION. - - METHODS: - multiply_by_two IMPORTING num TYPE i - RETURNING VALUE(result) TYPE i. - -... -"Class implementation part -CLASS cl_class_under_test IMPLEMENTATION. - -METHOD multiply_by_two. - result = num * 2. -ENDMETHOD. -... - -``` - -Example: Test method for the example method *multiply_by_two* in the production code - -As mentioned above, in simple cases, there may not be any dependend-on component in (a method of) the production code. Therefore, the functionality of the method in the production code is tested by simply comparing the value of the actual value (the value that is returned by the method, for example) and the expected value. - -``` abap -"The code in this snippet refers to the test class in the test include. -... - -"Test class, implementation part -CLASS ltc_test_class IMPLEMENTATION. - - "Test method implementation - METHOD some_test_method. - "given - "Creating an object of the class under test - "Assumption: A reference variable has been declared (DATA ref_cut TYPE REF TO cl_class_under_test.) - "A variable declared inline may not be the best choice if there is more than one - "test method in the class that also needs an object of the class under test. - "See also the setup method further down in this context. - ref_cut = NEW #( ). - "DATA(ref_cut) = NEW cl_class_under_test( ). - - "when - "Calling method that is to be tested - "As an example, 5 is inserted. The result should be 10, which is then checked. - DATA(result) = ref_cut->multiply_by_two( 5 ). - - "then - "Assertion - cl_abap_unit_assert=>assert_equals( - act = result - exp = 10 ). - -"Further optional parameters specified -* cl_abap_unit_assert=>assert_equals( -* act = result -* exp = 10 -* msg = |The result of 5 multiplied by 2 is wrong. It is { result }.| "Error message -* quit = if_abap_unit_constant=>quit-no ). "No test termination - - ENDMETHOD. - -ENDCLASS. -``` - -

⬆️ back to top

- -### Special Methods for Implementing the Test Fixture -- Special private methods for implementing the test [fixture](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenfixture_glosry.htm), which may include test data and test objects among others, can be included in the local test class. -- They are not test methods and the `FOR TESTING` addition cannot be used. -- They have no parameters. -- Instance methods: - - `setup`: Executed before each execution of a test method of a test class - - `teardown`: Executed after each execution of a test method of a test class -- Static methods - - `class_setup`: Executed once before all tests of the class - - `class_teardown`: Executed once after all tests of the class - -Example: - -``` abap -"Test class, declaration part -CLASS ltc_test_class DEFINITION - FOR TESTING - RISK LEVEL HARMLESS - DURATION SHORT. - - PRIVATE SECTION. - DATA ref_cut TYPE REF TO cl_class_under_test. - - METHODS: - "special methods - setup, - teardown, - - "test methods - some_test_method FOR TESTING, - another_test_method FOR TESTING. - -ENDCLASS. - -"Test class, implementation part -CLASS ltc_test_class IMPLEMENTATION. - - METHOD setup. - "Creating an object of the class under test - ref_cut = NEW #( ). - - "Here goes, for example, code for preparing the test data, - "e.g. filling a database table used for test methods. - ... - ENDMETHOD. - - - METHOD some_test_method. - - "Method call - DATA(result) = ref_cut->multiply_by_two( 5 ). - - "Assertion - cl_abap_unit_assert=>... - - ENDMETHOD. - - METHOD another_test_method. - "Another method to be tested, it can also use the centrally created instance. - - "Calling method - DATA(result) = ref_cut->multiply_by_three( 6 ). - - "Assertion - cl_abap_unit_assert=>... - - ENDMETHOD. - - METHOD teardown. - "Here goes, for example, code to undo the test data preparation - "during the setup method (e.g. deleting the inserted database table entries). - ... - ENDMETHOD. - -ENDCLASS. -``` - -> **💡 Note**
-> You can also specify helper methods, for example, for recurring tasks such as the assertions. - -

⬆️ back to top

- -## Handling Dependencies - -The code snippets above covered test classes and methods in simple contexts without dependend-on components (DOC) in the production code. In more complex cases with dependencies in the production code, there are ways to deal with them. You can create test doubles and inject them into the production code during the test run. - -It is assumed that you have identified the dependend-on components (DOC) in a method in your production code. They were isolated (which may have involved some major rebuilding if the code is not created from scratch and the requirements for unit testing were taken into account, e.g. by creating an interface), and you want to replace them with a test double (by injection) so that your code can be properly tested without dependencies. - - -**Creating/Implementing test doubles** - -As recommended, you should ideally have an interface to the DOC. - -There are multiple ways to implement test doubles manually: -- Interface is available for DOC - - You simply create a local test double by implementing interface methods to create test data. See the code snippet in the *Including Interfaces* section. - - As mentioned above, the `PARTIALLY IMPLEMENTED` addition is useful (and only possible) for interfaces in test classes. -- No interface is available for the DOC - - You can create your own local interface, adapt your production code accordingly, and implement interface methods. -- If the DOC is a method in a class that allows inheritance (i.e. it is not defined as `FINAL`), you can inherit from the class and redefine methods for which you need a test double. - -> **💡 Note**
-> Instead of manually creating test doubles, the [ABAP OO Test Double Framework](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/804c251e9c19426cadd1395978d3f17b.html?locale=en-US) helps you create test doubles automatically. - -**Injecting the test doubles** - -As described [here](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/04a2d0fc9cd940db8aedf3fa29e5f07e.html?locale=en-US), there are multiple techniques for injecting test doubles to ensure that the test doubles are used during the test run. - -Among them, there are the following. They are demonstrated in the executable example. Check the code and comments in the [global class](./src/zcl_demo_abap_unit_test.clas.abap) and [test include](./src/zcl_demo_abap_unit_test.clas.testclasses.abap) of the example. -- Constructor injection: The test double is passed as a parameter to the instance constructor `constructor` of the class under test. -- Setter injection: The test double is passed as a parameter to a setter method. -- Parameter injection: The test double is passed as a parameter to the tested method (i.e. an optional importing parameter) in the class under test. -- Back door injection: A *back door* is created to inject a test double into the class under test. This *back door* is implemented by granting [friendship](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenfriend_glosry.htm) to the test class. This makes internal attributes of the class under test accessible from the test class. - -

⬆️ back to top

- -### Test Seams -- Seams are sections of the production source code that can be dynamically included or replaced. -- In ABAP, [test seams](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abentest_seam_glosry.htm) can be used to replace source code in the production code by an injection when running unit tests. For more informarion, see [here](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abaptest-seam.htm). -- This is particularly useful in situations where tests cannot be executed properly or are even prevented from doing so. For example: - - Authorization checks - - Reading or modifying persistent data from the database - - Creating test doubles -- If the code is not executed in the context of a unit test, there is no injection. The original code (i.e. the code in the block `TEST-SEAM ... END-TEST-SEAM`) is executed. -- A test seam can also be empty, and then some code can be injected where the test seam is declared. - - -Test seams can be implemented using the following syntax: - -``` abap -"The code in this snippet refers to the production code. - -... - -DATA some_table TYPE TABLE OF dbtab WITH EMPTY KEY. - -"TEST-SEAM + name of the test seam ... END-TEST-SEAM define a -"code block that is to be replaced when running unit tests. - -TEST-SEAM select_from_db. - SELECT * FROM dbtab - INTO TABLE @some_table. -END-TEST-SEAM. - -... -``` - -``` abap -"The code in this snippet refers to the code in the test class. - -... - -"TEST-INJECTION + name of the test seam ... END-TEST-INJECTION define a code -"block to replace the code block in the production code when running unit tests. -"In the example below, the DOC (a database access) is solved by providing local -"test data. - -TEST-INJECTION select_from_db. - some_table = VALUE #( - ( ... ) - ( ... ) ). -END-TEST-INJECTION. - -... -``` - -

⬆️ 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). - -The focus of this cheat sheet is on running individual tests in a class that can be run directly in ADT. In your class in ADT (for example, in the class of the demonstration example), choose `Ctrl + Shift + F10` to run all tests in a class. You can also right-click anywhere in the code of the class and choose *Run as → ABAP Unit Test*. To run individual test classes or methods, place the cursor on the class/method name and run the unit test. - -The results of a test run are displayed and can be evaluated in the *ABAP Unit* tab in ADT. The *Failure Trace* section provides information about any errors found. - -If you are interested in the test coverage, you can choose `Ctrl + 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 in ADT and see what code was tested and what was not. - -For more information about evaluating ABAP unit test results, see [here](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/4ec49c5b6e391014adc9fffe4e204223.html?locale=en-US). - -

⬆️ back to top

- -## More Information - -- openSAP course: [Writing Testable Code for ABAP](https://open.sap.com/courses/wtc1.OpenSAP+WTC1_W1U5+Writing+Testable+Code+for+ABAPComent) -- ABAP Keyword Documentation - - [ABAP Unit](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenabap_unit.htm) - - [Testing repository objects](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abentest_relations.htm) -- SAP Help Portal: - - [Unit Testing with ABAP Unit](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/08c60b52cb85444ea3069779274b43db.html?locale=en-US) - - [ABAP Unit](https://help.sap.com/docs/ABAP_PLATFORM_NEW/ba879a6e2ea04d9bb94c7ccd7cdac446/491cfd8926bc14cde10000000a42189b.html?locale=en-US) - - The [ABAP OO Test Double Framework](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/804c251e9c19426cadd1395978d3f17b.html?locale=en-US) based on class `CL_ABAP_TESTDOUBLE` simplifies and standardizes the creation and configuration of test doubles. - - Development Guide for the ABAP RESTful Application Programming Model: [Testing different artifacts of RAP business objects and related OData services](https://help.sap.com/docs/btp/sap-abap-restful-application-programming-model/test?locale=en-US) - -

⬆️ back to top

- -## Executable Example - -[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 steps to import and run the code are outlined [here](README.md#-getting-started-with-the-examples). + + +# ABAP Unit Tests + +- [ABAP Unit Tests](#abap-unit-tests) + - [Unit Tests in ABAP](#unit-tests-in-abap) + - [High-Level Steps for ABAP Unit Tests](#high-level-steps-for-abap-unit-tests) + - [Creating Test Classes](#creating-test-classes) + - [Creating and Implementing Test Methods](#creating-and-implementing-test-methods) + - [Special Methods for Implementing the Test Fixture](#special-methods-for-implementing-the-test-fixture) + - [Handling Dependencies](#handling-dependencies) + - [Test Seams](#test-seams) + - [Running and Evaluating ABAP Unit Tests](#running-and-evaluating-abap-unit-tests) + - [More Information](#more-information) + - [Executable Example](#executable-example) + + +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. + +> **💡 Note**
+> - This cheat sheet focuses on testing methods. +> - See the [More Information](#more-information) section for links to more in-depth information. +> - The executable examples are **not** suitable role models for ABAP unit tests. They are intended to give you a rough idea. You should always work out your own solution for each individual case. + +## Unit Tests in ABAP +- Unit tests ... + - ensure the functional correctness of individual software units (i.e. a unit of code whose execution has a verifiable effect). + - are designed to test that the individual components of a larger software unit work correctly during the development and quality assurance phases. Typically, such individual software units are methods (the focus of this cheat sheet). + - must be created and run by developers. +- In ABAP, developers have [ABAP Unit](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenabap_unit_glosry.htm) - a test tool integrated into the ABAP runtime framework - at their disposal. It can be used to run individual or mass tests, and to evaluate test results. Note that comprehensive test runs can be performed using the [ABAP Test Cockpit](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenabap_test_cockpit_glosry.htm) +- In ABAP programs, individual unit tests are implemented as [test methods](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abentest_method_glosry.htm) of local [test classes](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abentest_class_glosry.htm). + +

⬆️ back to top

+ +## High-Level Steps for ABAP Unit Tests + +- Identify dependend-on components (DOC) in your production code (i.e. your class/method) that need to be tested, and prepare the code for testing. + - Examples of DOCs: In your production code, a method is called that is outside of your code. Or, for example, whenever there is an interaction with the database. + - This means that there are dependencies that need to be taken into account for a unit test. These dependencies should be isolated and replaced by a test double. +- Create/Implement test doubles + - You can create test doubles manually. For example, you hardcode the test data to be used when the test is executed. Note: Automatic creation is not covered here. + - Ideally, the testability of your code has been prepared by providing interfaces to the DOC. Interfaces facilitate the testability because you can simply implement the interface methods. +- Inject the test doubles to ensure that the test data is used during the test run. +- Create test classes and methods +- Run unit tests + +> **💡 Note**
+> Of course, if there are no dependend-on components in your production code, you can skip the considerations of dependency isolation, test doubles and their injection into the production code. + +

⬆️ back to top

+ +## Creating Test Classes + +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 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. +- can contain test methods, the special methods for the [fixture](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenfixture_glosry.htm), and other components. + - It is recommended that all components required for ABAP unit tests are defined in test classes only (so that they cannot be generated in production systems and cannot be addressed by production code). The components also include test doubles and other helper classes that do not contain test methods. + + +The skeleton of a test class might look like this: +``` abap +"Test class in the test include +CLASS ltc_test_class DEFINITION + FOR TESTING "Defines a class to be used in ABAP Unit + RISK LEVEL HARMLESS "Defines risk level, options: HARMLESS/CRITICAL/DANGEROUS + DURATION SHORT. "Expected test execution time, options: SHORT/MEDIUM/LONG + + ... + +ENDCLASS. + +CLASS ltc_test_class IMPLEMENTATION. + ... +ENDCLASS. +``` + +> **💡 Note**
+> - `FOR TESTING` can be used for multiple purposes: +> - Creating a test class containing test methods +> - Creating a test double +> - Creating helper methods to support ABAP unit tests +> - Note the possible [syntax options](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapclass_options.htm) before `FOR TESTING` +> - Optional addition `RISK LEVEL ...`: +> - `CRITICAL`: test changes system settings or customizing data (default) +> - `DANGEROUS`: test changes persistent data +> - `HARMLESS`: test does not change system settings or persistent data +> - Optional addition `DURATION ...`: +> - `SHORT`: execution time of only a few seconds is expected +> - `MEDIUM`: execution time of about one minute is expected +> - `LONG`: execution time of more than one minute is expected +> - To create a class in ADT, type "test" in the "Test Classes" tab and choose `CTRL + SPACE` to display the template suggestions. You can then choose "testClass – Test class (ABAP Unit)". The skeleton of a test class is automatically generated. + +To test protected or private methods, you must declare [friendship](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenfriend_glosry.htm) with the class to be tested (class under test). +Example: + +``` abap +"The code in this snippet refers to the test class in the test include. +"Test class, declaration part +CLASS ltc_test_class DEFINITION + FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT. + + ... + +ENDCLASS. + +"Declaring friendship +CLASS cl_class_under_test DEFINITION LOCAL FRIENDS ltc_test_class. + +"Test class, implementation part +CLASS ltc_test_class IMPLEMENTATION. + ... +ENDCLASS. +``` + +If you have multiple test classes in the test include, you can place the friendship declaration for all classes at the top, for example, as follows. Note the [`DEFERRED`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapclass_deferred.htm) addition. + +``` abap +"Test include + +CLASS ltc_test_class_1 DEFINITION DEFERRED. +CLASS ltc_test_class_2 DEFINITION DEFERRED. +CLASS cl_class_under_test DEFINITION LOCAL FRIENDS ltc_test_class_1 + ltc_test_class_2. + +CLASS ltc_test_class_1 DEFINITION + FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT. + ... +ENDCLASS. + +CLASS ltc_test_class_1 IMPLEMENTATION. + ... +ENDCLASS. + +CLASS ltc_test_class_2 DEFINITION + FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT. + ... +ENDCLASS. + +CLASS ltc_test_class_2 IMPLEMENTATION. + ... +ENDCLASS. +``` + + +**Including Interfaces** +- Usually, all non-optional interface methods must be implemented. +- When you use the `PARTIALLY IMPLEMENTED` addition in test classes, you are not forced to implement all of the methods. +- It is particularly useful for interfaces to implement test doubles, and not all methods are necessary. + +Example of creating a test double: + +``` abap +"Test double class in a test include +CLASS ltd_test_double DEFINITION FOR TESTING. + PUBLIC SECTION. + INTERFACES some_intf PARTIALLY IMPLEMENTED. +ENDCLASS. + +CLASS ltd_test_double IMPLEMENTATION. + METHOD some_intf~some_meth. + ... + ENDMETHOD. +ENDCLASS. +``` + +

⬆️ back to top

+ +## Creating and Implementing Test Methods + +Test methods ... +- are special [instance methods](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abeninstance_method_glosry.htm) of a test class in which a test is implemented. + - As with test classes, the `FOR TESTING` addition also applies to the test method declaration. + - Note that there are other syntax options, such as [`ABSTRACT` and others](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapmethods_testing.htm). +- are called by the ABAP Unit framework during a test run. +- are used to call units of production code and to check the result. + - The results are checked using methods of the class `CL_ABAP_UNIT_ASSERT`. +- should be private or protected if the methods are inherited. +- are called in an undefined order. +- have no parameters. + +**Creating Test Methods** + +Example: +``` abap +"Test class in a test include +"Test class declaration part +CLASS ltc_test_class DEFINITION + FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT. + + PRIVATE SECTION. + "Note: As a further component in test classes, usually, a reference variable + "is created for an instance of the class under test. + DATA ref_cut TYPE REF TO cl_class_under_test. + + "Test method declaration + METHODS some_test_method FOR TESTING. + +ENDCLASS. + +"Test class implementation part +CLASS ltc_test_class IMPLEMENTATION. + + METHOD some_test_method. + ... "Here goes the implementation of the test. + ENDMETHOD. + +ENDCLASS. +``` + +**Implementing Test Methods** +- The implementation ideally follows the *given-when-then* pattern. + - *given*: Preparing the test, e.g. creating an instance of the class under test and a local test double (to inject the test double into the class under test) + - *when*: Calling the procedure to be tested + - *then*: Checking and evaluating the test result using the static methods of the `CL_ABAP_UNIT_ASSERT` class +- The following points cover a selection of static methods of the class `CL_ABAP_UNIT_ASSERT` that can be used for the checks: + - `ASSERT_EQUALS`: Checks whether two data objects are the same. + - `ASSERT_BOUND`: Checks whether a reference variable is bound. + - `ASSERT_NOT_BOUND`: Negation of the one above. + - `ASSERT_INITIAL`: Checks whether a data object has its initial value. + - `ASSERT_NOT_INITIAL`: Negation of the one above. + - `ASSERT_SUBRC`: Checks the value of `sy-subrc`. + - `FAIL`: Triggers an error. +- For the class and methods, as well as the paramters, check the F2 information in the ADT. Depending on the method used, parameters can (or must) be specified. To name a few: + - `ACT`: of type `ANY` (in most cases), non-optional importing parameter of the methods, specifies a data object that is to be verified + - `EXP`: of type `ANY` (in most cases), a data object holding the expected value (non-optional for the `ASSERT_EQUALS` method) + - `MSG`: of type `CSEQUENCE`, error description + - `QUIT`: of type `INT1`, the specification affects the unit test flow control in case of an error, e.g. the constant `if_abap_unit_constant=>quit-no` can be used to determine that the unit test should not be terminated in case of an error + +The following code snippet shows a class declaration and implementation part in the production code. +A test method is to be created for the method. + +``` abap +"The code in this snippet refers to the production code in the global class. +"It shows a method declaration for a simple calculation in the class under test. + +"Class declaration part +CLASS cl_class_under_test DEFINITION + PUBLIC + CREATE PUBLIC. + + PRIVATE SECTION. + + METHODS: + multiply_by_two IMPORTING num TYPE i + RETURNING VALUE(result) TYPE i. + +... +"Class implementation part +CLASS cl_class_under_test IMPLEMENTATION. + +METHOD multiply_by_two. + result = num * 2. +ENDMETHOD. +... + +``` + +Example: Test method for the example method *multiply_by_two* in the production code + +As mentioned above, in simple cases, there may not be any dependend-on component in (a method of) the production code. Therefore, the functionality of the method in the production code is tested by simply comparing the value of the actual value (the value that is returned by the method, for example) and the expected value. + +``` abap +"The code in this snippet refers to the test class in the test include. +... + +"Test class, implementation part +CLASS ltc_test_class IMPLEMENTATION. + + "Test method implementation + METHOD some_test_method. + "given + "Creating an object of the class under test + "Assumption: A reference variable has been declared (DATA ref_cut TYPE REF TO cl_class_under_test.) + "A variable declared inline may not be the best choice if there is more than one + "test method in the class that also needs an object of the class under test. + "See also the setup method further down in this context. + ref_cut = NEW #( ). + "DATA(ref_cut) = NEW cl_class_under_test( ). + + "when + "Calling method that is to be tested + "As an example, 5 is inserted. The result should be 10, which is then checked. + DATA(result) = ref_cut->multiply_by_two( 5 ). + + "then + "Assertion + cl_abap_unit_assert=>assert_equals( + act = result + exp = 10 ). + +"Further optional parameters specified +* cl_abap_unit_assert=>assert_equals( +* act = result +* exp = 10 +* msg = |The result of 5 multiplied by 2 is wrong. It is { result }.| "Error message +* quit = if_abap_unit_constant=>quit-no ). "No test termination + + ENDMETHOD. + +ENDCLASS. +``` + +

⬆️ back to top

+ +### Special Methods for Implementing the Test Fixture +- Special private methods for implementing the test [fixture](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenfixture_glosry.htm), which may include test data and test objects among others, can be included in the local test class. +- They are not test methods and the `FOR TESTING` addition cannot be used. +- They have no parameters. +- Instance methods: + - `setup`: Executed before each execution of a test method of a test class + - `teardown`: Executed after each execution of a test method of a test class +- Static methods + - `class_setup`: Executed once before all tests of the class + - `class_teardown`: Executed once after all tests of the class + +Example: + +``` abap +"Test class, declaration part +CLASS ltc_test_class DEFINITION + FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT. + + PRIVATE SECTION. + DATA ref_cut TYPE REF TO cl_class_under_test. + + METHODS: + "special methods + setup, + teardown, + + "test methods + some_test_method FOR TESTING, + another_test_method FOR TESTING. + +ENDCLASS. + +"Test class, implementation part +CLASS ltc_test_class IMPLEMENTATION. + + METHOD setup. + "Creating an object of the class under test + ref_cut = NEW #( ). + + "Here goes, for example, code for preparing the test data, + "e.g. filling a database table used for test methods. + ... + ENDMETHOD. + + + METHOD some_test_method. + + "Method call + DATA(result) = ref_cut->multiply_by_two( 5 ). + + "Assertion + cl_abap_unit_assert=>... + + ENDMETHOD. + + METHOD another_test_method. + "Another method to be tested, it can also use the centrally created instance. + + "Calling method + DATA(result) = ref_cut->multiply_by_three( 6 ). + + "Assertion + cl_abap_unit_assert=>... + + ENDMETHOD. + + METHOD teardown. + "Here goes, for example, code to undo the test data preparation + "during the setup method (e.g. deleting the inserted database table entries). + ... + ENDMETHOD. + +ENDCLASS. +``` + +> **💡 Note**
+> You can also specify helper methods, for example, for recurring tasks such as the assertions. + +

⬆️ back to top

+ +## Handling Dependencies + +The code snippets above covered test classes and methods in simple contexts without dependend-on components (DOC) in the production code. In more complex cases with dependencies in the production code, there are ways to deal with them. You can create test doubles and inject them into the production code during the test run. + +It is assumed that you have identified the dependend-on components (DOC) in a method in your production code. They were isolated (which may have involved some major rebuilding if the code is not created from scratch and the requirements for unit testing were taken into account, e.g. by creating an interface), and you want to replace them with a test double (by injection) so that your code can be properly tested without dependencies. + + +**Creating/Implementing test doubles** + +As recommended, you should ideally have an interface to the DOC. + +There are multiple ways to implement test doubles manually: +- Interface is available for DOC + - You simply create a local test double by implementing interface methods to create test data. See the code snippet in the *Including Interfaces* section. + - As mentioned above, the `PARTIALLY IMPLEMENTED` addition is useful (and only possible) for interfaces in test classes. +- No interface is available for the DOC + - You can create your own local interface, adapt your production code accordingly, and implement interface methods. +- If the DOC is a method in a class that allows inheritance (i.e. it is not defined as `FINAL`), you can inherit from the class and redefine methods for which you need a test double. + +> **💡 Note**
+> Instead of manually creating test doubles, the [ABAP OO Test Double Framework](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/804c251e9c19426cadd1395978d3f17b.html?locale=en-US) helps you create test doubles automatically. + +**Injecting the test doubles** + +As described [here](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/04a2d0fc9cd940db8aedf3fa29e5f07e.html?locale=en-US), there are multiple techniques for injecting test doubles to ensure that the test doubles are used during the test run. + +Among them, there are the following. They are demonstrated in the executable example. Check the code and comments in the [global class](./src/zcl_demo_abap_unit_test.clas.abap) and [test include](./src/zcl_demo_abap_unit_test.clas.testclasses.abap) of the example. +- Constructor injection: The test double is passed as a parameter to the instance constructor `constructor` of the class under test. +- Setter injection: The test double is passed as a parameter to a setter method. +- Parameter injection: The test double is passed as a parameter to the tested method (i.e. an optional importing parameter) in the class under test. +- Back door injection: A *back door* is created to inject a test double into the class under test. This *back door* is implemented by granting [friendship](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenfriend_glosry.htm) to the test class. This makes internal attributes of the class under test accessible from the test class. + +

⬆️ back to top

+ +### Test Seams +- Seams are sections of the production source code that can be dynamically included or replaced. +- In ABAP, [test seams](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abentest_seam_glosry.htm) can be used to replace source code in the production code by an injection when running unit tests. For more informarion, see [here](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abaptest-seam.htm). +- This is particularly useful in situations where tests cannot be executed properly or are even prevented from doing so. For example: + - Authorization checks + - Reading or modifying persistent data from the database + - Creating test doubles +- If the code is not executed in the context of a unit test, there is no injection. The original code (i.e. the code in the block `TEST-SEAM ... END-TEST-SEAM`) is executed. +- A test seam can also be empty, and then some code can be injected where the test seam is declared. + + +Test seams can be implemented using the following syntax: + +``` abap +"The code in this snippet refers to the production code. + +... + +DATA some_table TYPE TABLE OF dbtab WITH EMPTY KEY. + +"TEST-SEAM + name of the test seam ... END-TEST-SEAM define a +"code block that is to be replaced when running unit tests. + +TEST-SEAM select_from_db. + SELECT * FROM dbtab + INTO TABLE @some_table. +END-TEST-SEAM. + +... +``` + +``` abap +"The code in this snippet refers to the code in the test class. + +... + +"TEST-INJECTION + name of the test seam ... END-TEST-INJECTION define a code +"block to replace the code block in the production code when running unit tests. +"In the example below, the DOC (a database access) is solved by providing local +"test data. + +TEST-INJECTION select_from_db. + some_table = VALUE #( + ( ... ) + ( ... ) ). +END-TEST-INJECTION. + +... +``` + +

⬆️ 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). + +The focus of this cheat sheet is on running individual tests in a class that can be run directly in ADT. In your class in ADT (for example, in the class of the demonstration example), choose `Ctrl + Shift + F10` to run all tests in a class. You can also right-click anywhere in the code of the class and choose *Run as → ABAP Unit Test*. To run individual test classes or methods, place the cursor on the class/method name and run the unit test. + +The results of a test run are displayed and can be evaluated in the *ABAP Unit* tab in ADT. The *Failure Trace* section provides information about any errors found. + +If you are interested in the test coverage, you can choose `Ctrl + 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 in ADT and see what code was tested and what was not. + +For more information about evaluating ABAP unit test results, see [here](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/4ec49c5b6e391014adc9fffe4e204223.html?locale=en-US). + +

⬆️ back to top

+ +## More Information + +- openSAP course: [Writing Testable Code for ABAP](https://open.sap.com/courses/wtc1.OpenSAP+WTC1_W1U5+Writing+Testable+Code+for+ABAPComent) +- ABAP Keyword Documentation + - [ABAP Unit](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenabap_unit.htm) + - [Testing repository objects](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abentest_relations.htm) +- SAP Help Portal: + - [Unit Testing with ABAP Unit](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/08c60b52cb85444ea3069779274b43db.html?locale=en-US) + - [ABAP Unit](https://help.sap.com/docs/ABAP_PLATFORM_NEW/ba879a6e2ea04d9bb94c7ccd7cdac446/491cfd8926bc14cde10000000a42189b.html?locale=en-US) + - The [ABAP OO Test Double Framework](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/804c251e9c19426cadd1395978d3f17b.html?locale=en-US) based on class `CL_ABAP_TESTDOUBLE` simplifies and standardizes the creation and configuration of test doubles. + - Development Guide for the ABAP RESTful Application Programming Model: [Testing different artifacts of RAP business objects and related OData services](https://help.sap.com/docs/btp/sap-abap-restful-application-programming-model/test?locale=en-US) + +

⬆️ back to top

+ +## Executable Example + +[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 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/16_Data_Types_and_Objects.md b/16_Data_Types_and_Objects.md index 63df76e..854b128 100644 --- a/16_Data_Types_and_Objects.md +++ b/16_Data_Types_and_Objects.md @@ -18,7 +18,7 @@ - [Assigning References to Data Reference Variables](#assigning-references-to-data-reference-variables) - [Creating Anonymous Data Objects](#creating-anonymous-data-objects) - [Constants and Immutable Variables](#constants-and-immutable-variables) - - [Type Conversion](#type-conversion) + - [Type Conversions and Assignments](#type-conversions-and-assignments) - [Terms Related to Data Types and Objects in a Nutshell](#terms-related-to-data-types-and-objects-in-a-nutshell) - [Notes on the Declaration Context](#notes-on-the-declaration-context) - [Excursions](#excursions) @@ -1106,7 +1106,7 @@ SELECT * FROM zdemo_abap_carr INTO TABLE @FINAL(itab_final_inl).

⬆️ back to top

-## Type Conversion +## Type Conversions and Assignments A value assignment means that the value of a data object is transferred to a target data object. If the data types of the source and target are compatible, the content is copied unchanged. If they are incompatible and a suitable [conversion rule](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenconversion_rule_glosry.htm) exists, the content is converted. The following cases must be distinguished with regard to the data type: - The source and target data types are compatible, i.e. all technical type properties match. The content is transferred from the source to the target without being converted. @@ -1120,7 +1120,7 @@ See the conversion rules for the different data types here: [Assignment and Conv > - Typically, assignements are made using the assignment operator `=`. If necessary and applicable, the type is converted implicitly. However, you can also use the conversion operator [`CONV`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenconstructor_expression_conv.htm) to convert types explicitly. > - For [lossless assignments](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenlossless_assignment_glosry.htm), the lossless operator [`EXACT`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenconstructor_expression_exact.htm) can be used to perform checks before the conversion is performed to ensure that only valid values are assigned and that no values are lost in assignments. > - In general, no checks are performed on assignments between compatible data objects. If a data object already contains an invalid value, for example, an invalid date or time in a date or time field, it is passed a valid value when the assignment is made to a compatible data object. -> - The `applies_to_data` method of the [RTTI](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenrun_time_type_identific_glosry.htm) class `cl_abap_datadescr` can be used to check type compatibility. See the executable example. +> - The `applies_to_data` method of the [RTTI](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenrun_time_type_identific_glosry.htm) class `cl_abap_datadescr` can be used to check type compatibility. See the executable example and the [Dynamic Programming cheat sheet](06_Dynamic_Programming.md). ```abap diff --git a/22_Misc_ABAP_Classes.md b/22_Misc_ABAP_Classes.md index 90c5922..8fc0f9b 100644 --- a/22_Misc_ABAP_Classes.md +++ b/22_Misc_ABAP_Classes.md @@ -27,6 +27,7 @@ - [Running Code in the Background](#running-code-in-the-background) - [Locking](#locking) - [Calling Services](#calling-services) + - [Generating ABAP Repository Objects](#generating-abap-repository-objects) This ABAP cheat sheet contains a selection of available ABAP classes, serving as a quick introduction, along with code snippets to explore the functionality in action. @@ -2427,4 +2428,48 @@ ENDCLASS. +

⬆️ back to top

+ +## Generating ABAP Repository Objects + + + + + + + + + +
Class Details/Code Snippet
XCO_CP_GENERATION +For creating, updating and deleting ABAP repository objects. More information: [Generation APIs](https://help.sap.com/docs/btp/sap-business-technology-platform/generation-apis)
The rudimentary snippet is taken from the executable example of the ABAP for Cloud Development cheat sheet. + +

+ +``` abap +... +DATA(n10_handler) = xco_cp_generation=>environment->dev_system( ... ). +DATA(n11_put) = n10_handler->create_put_operation( ). + +"Creating a domain +DATA(n12_doma_spec) = n11_put->for-doma->add_object( ... "e.g. 'ZDEMO_ABAP_STATUS' + )->set_package( ... + )->create_form_specification( ). +n12_doma_spec->set_short_description( 'Demo domain' ). +n12_doma_spec->set_format( xco_cp_abap_dictionary=>built_in_type->char( 10 ) ). +n12_doma_spec->fixed_values->add_fixed_value( 'BOOKED' + )->set_description( 'Booked' ). +n12_doma_spec->fixed_values->add_fixed_value( 'CANCELED' + )->set_description( 'Canceled' ). + +... + +"Executing the generation +TRY. + n11_put->execute( ). + CATCH cx_xco_gen_put_exception. +ENDTRY. +``` + +
+

⬆️ back to top

\ No newline at end of file diff --git a/23_Date_and_Time.md b/23_Date_and_Time.md index 6bf2c16..03a04f9 100644 --- a/23_Date_and_Time.md +++ b/23_Date_and_Time.md @@ -20,12 +20,12 @@ - [Time Stamp Calculations with XCO](#time-stamp-calculations-with-xco) - [Calculating Time Stamp Differences Using the Built-In Function `utclong_diff`](#calculating-time-stamp-differences-using-the-built-in-function-utclong_diff) - [`CONVERT UTCLONG`: Time Stamp (`utclong`) -\> Local Date/Time](#convert-utclong-time-stamp-utclong---local-datetime) - - [`CONVERT INTO UTCLONG`: Local Date/Time -\> Time Stamp (`utclong`)](#convert-into-utclong-local-datetime---time-stamp-utclong) + - [`CONVERT ... INTO UTCLONG`: Local Date/Time -\> Time Stamp (`utclong`)](#convert--into-utclong-local-datetime---time-stamp-utclong) - [`CL_ABAP_UTCLONG`: Utilities for Time Stamps (`utclong`)](#cl_abap_utclong-utilities-for-time-stamps-utclong) - [Time Stamps in Packed Numbers (types `timestamp`, `timestampl`)](#time-stamps-in-packed-numbers-types-timestamp-timestampl) - [`GET TIME STAMP`: Retrieving the Current Time Stamp](#get-time-stamp-retrieving-the-current-time-stamp) - [`CONVERT TIME STAMP`: Time Stamp in Packed Numbers -\> Local Date/Time](#convert-time-stamp-time-stamp-in-packed-numbers---local-datetime) - - [`CONVERT INTO TIME STAMP`: Local Date/Time -\> Time Stamp in Packed Numbers](#convert-into-time-stamp-local-datetime---time-stamp-in-packed-numbers) + - [`CONVERT ... INTO TIME STAMP`: Local Date/Time -\> Time Stamp in Packed Numbers](#convert--into-time-stamp-local-datetime---time-stamp-in-packed-numbers) - [`CL_ABAP_TSTMP`: Calculating and Converting Time Stamps in Packed Numbers](#cl_abap_tstmp-calculating-and-converting-time-stamps-in-packed-numbers) - [Excursion: Unix Time Stamps](#excursion-unix-time-stamps) - [Date, Time, and Time Stamps in String Templates](#date-time-and-time-stamps-in-string-templates) @@ -788,9 +788,9 @@ ENDTRY.

⬆️ back to top

-#### `CONVERT INTO UTCLONG`: Local Date/Time -> Time Stamp (`utclong`) +#### `CONVERT ... INTO UTCLONG`: Local Date/Time -> Time Stamp (`utclong`) -More information: [`CONVERT INTO UTCLONG`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapconvert_utclong.htm) +More information: [`CONVERT ... INTO UTCLONG`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapconvert_utclong.htm) ```abap DATA date2utcl TYPE d VALUE '20240101'. @@ -1004,9 +1004,9 @@ ASSERT sy-subrc = 12.

⬆️ back to top

-#### `CONVERT INTO TIME STAMP`: Local Date/Time -> Time Stamp in Packed Numbers +#### `CONVERT ... INTO TIME STAMP`: Local Date/Time -> Time Stamp in Packed Numbers -More information: [`CONVERT INTO TIME STAMP`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapconvert_date_time-stamp.htm) +More information: [`CONVERT ... INTO TIME STAMP`](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapconvert_date_time-stamp.htm) ```abap DATA date4conv TYPE d VALUE '20240101'. diff --git a/24_Misc_Builtin_Functions.md b/24_Misc_Builtin_Functions.md index ab00676..4641453 100644 --- a/24_Misc_Builtin_Functions.md +++ b/24_Misc_Builtin_Functions.md @@ -81,7 +81,7 @@ Boolean function that returns a truth value. Similar to boolc, it r

``` abap -"X +"abap_true DATA(xsdb1) = xsdbool( 3 > 1 ). "#X# @@ -216,7 +216,7 @@ DATA(cont15) = xsdbool( contains_any_not_of( val = hi end = abc ) ). matches -Comparing a search range. More optional parameters are available. +Comparing a search range of a value with a regular expression. More optional parameters are available (e.g. case, off, len).

``` abap