The ABAP class CL_ABAP_UNIT_ASSERT is a great class for asserting Actual and Expected data for ABAP unit testing. Asserting equal is comparing the Actual data with the Expected data that it is equal. If it is not equal than a failure will be raised.
The method ASSERT_EQUALS even supports deep structures.
As you can see in my blog ABAP BAPI BO Class Generator in paragraph “Testing the read method” I use deep structures a lot, for example for GET_DATA methods which return all BAPI data in a deep structures returning parameter, so I can use one returning parameter instead of multiple exporting parameters. Example:
This deep structure data can be used in unit testing.
Where the standard SAP ASSERT_EQUALS method a little bit lacks, is on asserting Standard tables. (CL_ABAP_UNIT_ASSERT version – SAP ABA version: 75D, SP: 2)
For non-ABAP developers: a standard table is a kind of internal memory / parameter table. These Standard tables are very important because almost all BAPIs make use of Standard tables.
I will first explain where it lacks and after that I will show you my solution.
To show the lacking of asserting Standard tables I created a test program with a deep structure type containing a hashed table, a sorted table and a standard table. And I created a deep structure variable for the Expected data and the Actual data filling these tables all with one line.
The types used in the test program:
The method ASSERT_EQUALS even supports deep structures.
As you can see in my blog ABAP BAPI BO Class Generator in paragraph “Testing the read method” I use deep structures a lot, for example for GET_DATA methods which return all BAPI data in a deep structures returning parameter, so I can use one returning parameter instead of multiple exporting parameters. Example:
Where the standard SAP ASSERT_EQUALS method a little bit lacks, is on asserting Standard tables. (CL_ABAP_UNIT_ASSERT version – SAP ABA version: 75D, SP: 2)
For non-ABAP developers: a standard table is a kind of internal memory / parameter table. These Standard tables are very important because almost all BAPIs make use of Standard tables.
I will first explain where it lacks and after that I will show you my solution.
Problem
To show the lacking of asserting Standard tables I created a test program with a deep structure type containing a hashed table, a sorted table and a standard table. And I created a deep structure variable for the Expected data and the Actual data filling these tables all with one line.
The types used in the test program:
The content from Actual (act) and Expected (exp) differ on field “FIELD_2”. See picture below:
For all the code of the test program, see “Appendix: ZZUT_ASSERT_EQ_STANDARD_TABLE”.
The result of the unit test (Ctrl + Shift + F10) is:
See that component TEST_HASHED_TABLE shows exactly the difference at index 1 in field FIELD_2.
See that component TEST_SORTED_TABLE shows also exactly the difference at index 1 in field FIELD_2.
See that component TEST_STANDARD_TABLE does not show the difference. It shows that the Actual table has a line which is not in the Expected table and visa versa.
By comparing the content of the both tables…
◉ [[AAAA|Test value field 1|Test value field 2]]
◉ [[AAAA|Test value field 1|Test value field 2 modified.]]
…the field values are separated by a pipe, so we can conclude it must be the 3rd field, which is FIELD_2.
However BAPI tables can contain often much more fields. For example structure BAPI_ALM_ORDER_OPERATION contains 120 fields and than comparing data in this way is not possible anymore.
Solution
I created a new class ZCL_ABAP_UNIT_ASSERT to solve the problem. See “Appendix ZCL_ABAP_UNIT_ASSERT” for the ABAP code.
This class compares standard tables in more detail.
The functionality is:
◉ It searches through the data (can be a deep structure) looking for all Standard tables.
◉ It first sorts the Actual and Expected table.
◉ Than it compares the number of lines. If they differ, than it will be show as an error.
◉ Than it checks the Actual content with the Expected content line by line up to a maximum of 10 differences.
◉ Than it clears Actual table and Expected table, so these won’t be checked anymore by the standard SAP ASSERT_EQUALS.
The result using method ZCL_ABAP_UNIT_ASSERT=>ASSERT_EQUALS is:
The messages for the standard table are not generated in the same failure as the one generated by the standard SAP ASSERT_EQUALS. So, now two failures are generated.
The first failure is for the Standard table by the custom ASSERT_EQUALS method.
The second failure is for the Hashed and Sorted table created by the standard SAP ASSERT_EQUALS method.
Failure 1: Critical Assertion Error: ‘Standard table: TEST_STANDARD_TABLE, index: 1, field: FIELD_2’
See that field TEST_STANDARD_TABLE has at row index 1 in field FIELD_2 a difference.
Remark: the row index is based on sorting the Standard table first.
The value difference is shown in the messages part:
Expected [Test value field 2 modified.] Actual [Test value field 2]
Failure 2: Critical Assertion Error: ‘Sc001_Test_Assert_Equals: ASSERT_EQUALS’
I selected now the second failure:
You can see that the line related to component TEST_STANDARD_TABLE…
“> Component [TEST_STANDARD_TABLE]: Table Type [S-Table[1×128] of … “
…is missing, because the Standard tables are now first checked by the custom ASSERT_EQUALS method and the result of that check is shown in the first failure.
Example of a line count difference.
I added one extra line to the Expected table of the Standard table. Now the test result is:
See failure is “…Table line count of … are not equal.’.
Expected table contains 2 lines, Actual table contains 1 line.
Example of more than 10 field differences
I added 15 more fields with all values differing from Actual versus Expected.
As you can see, for every differences a new failure is created up to a maximum of 10 differences. I maximized the number of differences to overcome to show too much differences.
On example field difference is FIELD_4. It expected is empty, but it actual contains 4.
Appendix: ZZUT_ASSERT_EQ_STANDARD_TABLE
REPORT ZZUT_ASSERT_EQ_STANDARD_TABLE.
CLASS unit_test_class DEFINITION FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS: sc001_test_assert_equals FOR TESTING.
ENDCLASS. "unit_Test_Class
CLASS unit_test_class IMPLEMENTATION.
METHOD sc001_test_assert_equals.
TYPES:
BEGIN OF test_record_type,
key_field TYPE c LENGTH 4,
field_1 TYPE c LENGTH 20,
field_2 TYPE c LENGTH 40,
END OF test_record_type.
TYPES:
BEGIN OF test_data_set_type,
test_hashed_table TYPE HASHED TABLE OF test_record_type
WITH UNIQUE KEY key_field,
test_sorted_table TYPE SORTED TABLE OF test_record_type
WITH UNIQUE KEY key_field,
test_standard_table TYPE STANDARD TABLE OF test_record_type
WITH DEFAULT KEY,
END OF test_data_set_type.
"Fill test data
DATA(act_test_record_1) = VALUE test_record_type(
key_field = 'AAAA'
field_1 = 'Test value field 1'
field_2 = 'Test value field 2'
).
DATA(exp_test_record_1) = VALUE test_record_type(
key_field = act_test_record_1-key_field
field_1 = act_test_record_1-field_1
field_2 = act_test_record_1-field_2 && | modified.|
).
DATA act_test_data_set TYPE test_data_set_type.
DATA exp_test_data_set TYPE test_data_set_type.
INSERT act_test_record_1 INTO TABLE act_test_data_set-test_hashed_table.
INSERT act_test_record_1 INTO TABLE act_test_data_set-test_sorted_table.
APPEND act_test_record_1 TO act_test_data_set-test_standard_table.
INSERT exp_test_record_1 INTO TABLE exp_test_data_set-test_hashed_table.
INSERT exp_test_record_1 INTO TABLE exp_test_data_set-test_sorted_table.
APPEND exp_test_record_1 TO exp_test_data_set-test_standard_table.
cl_abap_unit_assert=>assert_equals(
act = act_test_data_set
exp = exp_test_data_set ).
ENDMETHOD.
ENDCLASS.
Appendix: ZCL_ABAP_UNIT_ASSERT
CLASS zcl_abap_unit_assert DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
CLASS-METHODS assert_equals
IMPORTING field_name TYPE char30 OPTIONAL
VALUE(act) TYPE any
VALUE(exp) TYPE any
ignore_hash_sequence TYPE abap_bool DEFAULT abap_false
tol TYPE f OPTIONAL
msg TYPE csequence OPTIONAL
level TYPE int1 DEFAULT if_aunit_constants=>severity-medium
quit TYPE int1 DEFAULT if_aunit_constants=>method
skip_default_assert TYPE abap_bool DEFAULT abap_false
RETURNING VALUE(assertion_failed) TYPE abap_bool.
PROTECTED SECTION.
CLASS-METHODS assert_equals_by_ref
IMPORTING field_name TYPE char30 OPTIONAL
ignore_hash_sequence TYPE abap_bool
tol TYPE f
msg TYPE csequence
level TYPE int1
quit TYPE int1
skip_default_assert TYPE abap_bool
CHANGING act TYPE any
exp TYPE any
RETURNING VALUE(assertion_failed) TYPE abap_bool.
CLASS-METHODS assert_standard_table
IMPORTING iv_clear_table_ind TYPE abap_bool
iv_table_name TYPE typename
iv_quit TYPE int1
CHANGING ct_act_table TYPE STANDARD TABLE
ct_exp_table TYPE STANDARD TABLE
RETURNING VALUE(rv_assert_failed) TYPE abap_bool.
ENDCLASS.
CLASS ZCL_ABAP_UNIT_ASSERT IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_ABAP_UNIT_ASSERT=>ASSERT_EQUALS
* +-------------------------------------------------------------------------------------------------+
* | [--->] FIELD_NAME TYPE CHAR30(optional)
* | [--->] ACT TYPE ANY
* | [--->] EXP TYPE ANY
* | [--->] IGNORE_HASH_SEQUENCE TYPE ABAP_BOOL (default =ABAP_FALSE)
* | [--->] TOL TYPE F(optional)
* | [--->] MSG TYPE CSEQUENCE(optional)
* | [--->] LEVEL TYPE INT1 (default =IF_AUNIT_CONSTANTS=>SEVERITY-MEDIUM)
* | [--->] QUIT TYPE INT1 (default =IF_AUNIT_CONSTANTS=>METHOD)
* | [--->] SKIP_DEFAULT_ASSERT TYPE ABAP_BOOL (default =ABAP_FALSE)
* | [<-()] ASSERTION_FAILED TYPE ABAP_BOOL
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD assert_equals.
"Pass act and exp by changing
assertion_failed =
assert_equals_by_ref(
EXPORTING field_name = field_name
ignore_hash_sequence = ignore_hash_sequence
tol = tol
msg = msg
level = level
quit = if_aunit_constants=>no
skip_default_assert = skip_default_assert
CHANGING act = act
exp = exp ).
"Handling quit
CASE quit.
* when if_Aunit_Constants=>program.
* raise exception type cx_Aunit_Sbx_Quit_Test_Prog.
WHEN if_aunit_constants=>class.
RAISE EXCEPTION TYPE cx_aunit_sbx_quit_test_class.
WHEN if_aunit_constants=>method.
RAISE EXCEPTION TYPE cx_aunit_sbx_quit_test_method.
WHEN if_aunit_constants=>quit-no.
"
WHEN OTHERS.
RAISE EXCEPTION TYPE cx_aunit_sbx_quit_test_method.
ENDCASE.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Protected Method ZCL_ABAP_UNIT_ASSERT=>ASSERT_EQUALS_BY_REF
* +-------------------------------------------------------------------------------------------------+
* | [--->] FIELD_NAME TYPE CHAR30(optional)
* | [--->] IGNORE_HASH_SEQUENCE TYPE ABAP_BOOL
* | [--->] TOL TYPE F
* | [--->] MSG TYPE CSEQUENCE
* | [--->] LEVEL TYPE INT1
* | [--->] QUIT TYPE INT1
* | [--->] SKIP_DEFAULT_ASSERT TYPE ABAP_BOOL
* | [<-->] ACT TYPE ANY
* | [<-->] EXP TYPE ANY
* | [<-()] ASSERTION_FAILED TYPE ABAP_BOOL
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD assert_equals_by_ref.
DATA(lr_act_type_descr) = cl_abap_typedescr=>describe_by_data( act ).
CASE lr_act_type_descr->kind.
WHEN cl_abap_typedescr=>kind_table.
DATA(lr_act_table_descr) = CAST cl_abap_tabledescr( lr_act_type_descr ).
CASE lr_act_table_descr->table_kind.
WHEN cl_abap_tabledescr=>tablekind_std.
FIELD-SYMBOLS <act_standard_table> TYPE STANDARD TABLE.
FIELD-SYMBOLS <exp_standard_table> TYPE STANDARD TABLE.
ASSIGN act TO <act_standard_table>.
ASSIGN exp TO <exp_standard_table>.
DATA(lv_std_table_assert_failed) =
assert_standard_table(
EXPORTING iv_clear_table_ind = abap_true
iv_table_name = field_name "Todo: delete Hungarian naming
iv_quit = quit
CHANGING ct_act_table = <act_standard_table>
ct_exp_table = <exp_standard_table> ).
IF lv_std_table_assert_failed = abap_true.
assertion_failed = abap_true.
ENDIF.
REFRESH <act_standard_table>.
ENDCASE.
WHEN cl_abap_typedescr=>kind_struct.
DATA(lr_act_struct_descr) = CAST cl_abap_structdescr( lr_act_type_descr ).
DATA(lt_components) = lr_act_struct_descr->get_components( ).
LOOP AT lt_components
ASSIGNING FIELD-SYMBOL(<ls_component>).
"Todo: testen met structure includes
ASSIGN COMPONENT <ls_component>-name
OF STRUCTURE act
TO FIELD-SYMBOL(<component_act>).
ASSIGN COMPONENT <ls_component>-name
OF STRUCTURE exp
TO FIELD-SYMBOL(<component_exp>).
DATA(lv_assert_failed) =
zcl_abap_unit_assert=>assert_equals_by_ref(
EXPORTING
field_name =
COND string( WHEN field_name IS NOT INITIAL THEN field_name && |-| ELSE || ) &&
<ls_component>-name
ignore_hash_sequence = ignore_hash_sequence
tol = tol
msg = msg
level = if_aunit_constants=>severity-medium
quit = if_aunit_constants=>quit-test
skip_default_assert = abap_true
CHANGING
act = <component_act>
exp = <component_exp> ).
IF lv_assert_failed = abap_true.
assertion_failed = abap_true.
ENDIF.
ENDLOOP.
"Todo: loop
ENDCASE.
IF skip_default_assert = abap_false.
"Assert the rest in a standard way.
DATA(lv_sap_assertion_failed) = cl_abap_unit_assert=>assert_equals(
act = act
exp = exp
ignore_hash_sequence = ignore_hash_sequence
tol = tol
msg = msg
level = if_aunit_constants=>severity-medium
quit = if_aunit_constants=>quit-test ).
IF lv_sap_assertion_failed = abap_true.
assertion_failed = abap_true.
ENDIF.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Protected Method ZCL_ABAP_UNIT_ASSERT=>ASSERT_STANDARD_TABLE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_CLEAR_TABLE_IND TYPE ABAP_BOOL
* | [--->] IV_TABLE_NAME TYPE TYPENAME
* | [--->] IV_QUIT TYPE INT1
* | [<-->] CT_ACT_TABLE TYPE STANDARD TABLE
* | [<-->] CT_EXP_TABLE TYPE STANDARD TABLE
* | [<-()] RV_ASSERT_FAILED TYPE ABAP_BOOL
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD assert_standard_table.
"**************************************************************
"Sort tables
SORT ct_act_table.
SORT ct_exp_table.
"**************************************************************
"Check line count
DATA(act_table_line_count) = lines( ct_act_table ).
DATA(exp_table_line_count) = lines( ct_exp_table ).
DATA msg TYPE c LENGTH 200.
DATA(table_line_count_failed) = cl_abap_unit_assert=>assert_equals(
act = act_table_line_count
exp = exp_table_line_count
msg =
|Table line count | &&
COND string( WHEN iv_table_name IS NOT INITIAL THEN |of | && iv_table_name ELSE || ) &&
| are not equal.|
quit = if_aunit_constants=>no ).
"**************************************************************
"Set max. field differences
IF table_line_count_failed = abap_true.
DATA(field_diff_max_count) = 5.
rv_assert_failed = abap_true.
ELSE.
field_diff_max_count = 10.
ENDIF.
"**************************************************************
"Loop at records
DATA(field_diff_count) = 0.
LOOP AT ct_act_table
ASSIGNING FIELD-SYMBOL(<ls_act_record>).
DATA(lv_tabix) = sy-tabix.
READ TABLE ct_exp_table
INDEX lv_tabix
ASSIGNING FIELD-SYMBOL(<ls_exp_record>).
DATA(lr_struct_descr) = CAST cl_abap_structdescr( cl_abap_structdescr=>describe_by_data( <ls_act_record> ) ).
DATA(lt_components) = lr_struct_descr->get_components( ).
LOOP AT lt_components
ASSIGNING FIELD-SYMBOL(<ls_comp>).
ASSIGN COMPONENT <ls_comp>-name
OF STRUCTURE <ls_act_record>
TO FIELD-SYMBOL(<ls_act_value>).
ASSIGN COMPONENT <ls_comp>-name
OF STRUCTURE <ls_exp_record>
TO FIELD-SYMBOL(<ls_exp_value>).
DATA(lv_assert_failed) =
cl_abap_unit_assert=>assert_equals(
act = <ls_act_value>
exp = <ls_exp_value>
msg = |Standard table: | && iv_table_name && |, | &&
|index: | && lv_tabix && |, | &&
|field: | && <ls_comp>-name
quit = if_aunit_constants=>no ).
IF lv_assert_failed = abap_true.
field_diff_count = field_diff_count + 1.
IF field_diff_count >= field_diff_max_count.
cl_abap_unit_assert=>fail(
msg = |Maximum number of field differences { field_diff_max_count } is reached. There might be more differences.|
quit = if_aunit_constants=>no ).
EXIT.
ENDIF.
rv_assert_failed = abap_true.
ENDIF.
ENDLOOP.
ENDLOOP.
IF iv_clear_table_ind = abap_true.
REFRESH ct_act_table.
REFRESH ct_exp_table.
ENDIF.
ENDMETHOD.
ENDCLASS.
No comments:
Post a Comment