diff --git a/changelog.txt b/changelog.txt index 008e42ff..30782587 100644 --- a/changelog.txt +++ b/changelog.txt @@ -14,6 +14,7 @@ Whenever you upgrade code pal for ABAP, it is highly recommended to execute the 2021-04-** v.1.14.0 ------------------ ++ Unit Test Without/With Invalid Assert (#288) + Prefer NEW to CREATE OBJECT (#283) * Standard functions in Prefer IS NOT to NOT IS (#338) * In the profile feature, you can add all the missing checks (#346) diff --git a/docs/check_documentation.md b/docs/check_documentation.md index 7afa1dec..453ffcd9 100644 --- a/docs/check_documentation.md +++ b/docs/check_documentation.md @@ -51,3 +51,4 @@ - [Self-Reference](checks/self-reference.md) - [TEST-SEAM Statement Usage](checks/test-seam-usage.md) - [Unit-Test Coverages (Statement, Branch and Procedure)](checks/unit-test-coverages.md) +- [Unit-Test Assert Validator](checks/unit_test_assert.md) diff --git a/docs/checks/unit_test_assert.md b/docs/checks/unit_test_assert.md new file mode 100644 index 00000000..1eb2aeff --- /dev/null +++ b/docs/checks/unit_test_assert.md @@ -0,0 +1,59 @@ +[code pal for ABAP](../../README.md) > [Documentation](../check_documentation.md) > [Unit-Test Assert Validator](unit_test_assert.md) + +## Unit-Test Assert Validator + +### What is the Intent of the Check? + +This check verifies invalid assertions in unit tests. +It supports the `CL_ABAP_UNIT_ASSERT=>ASSERT*` and `CL_AUNIT_ASSERT=>ASSERT*`. + +### How does the check work? + +It checks for actual (`act`) or expected (`exp`) invalid value(s), for instance: +- When both are using the same variable for the Assertion (which will always return TRUE); +- When both are hardcoded. + +### How to solve the issue? + +Fix the actual (`act`) or expected (`exp`) value(s) in the unit test assertion in order to achieve a meaningful and real Assertion. + +### What to do in case of exception? + +In exceptional cases (if any), you can suppress this finding by using the pseudo comment `"#EC UT_ASSERT` which has to be placed after the assertion statement: + +```abap +cl_abap_unit_assert=>assert_equals( act = sum + exp = sum ). "#EC UT_ASSERT +``` + +### Example + +Before the check: + +```abap +METHOD sum. + " given + DATA(first) = 10. + DATA(second) = 10. + " when + DATA(sum) = first + second. + " then + cl_abap_unit_assert=>assert_equals( act = sum + exp = sum ). +ENDMETHOD. +``` + +After the check: + +```abap +METHOD sum. + " given + DATA(first) = 10. + DATA(second) = 10. + " when + DATA(sum) = first + second. + " then + cl_abap_unit_assert=>assert_equals( act = sum + exp = 20 ). +ENDMETHOD. +``` diff --git a/src/checks/y_check_unit_test_assert.clas.abap b/src/checks/y_check_unit_test_assert.clas.abap new file mode 100644 index 00000000..8c48c9bd --- /dev/null +++ b/src/checks/y_check_unit_test_assert.clas.abap @@ -0,0 +1,94 @@ +CLASS y_check_unit_test_assert DEFINITION PUBLIC INHERITING FROM y_check_base CREATE PUBLIC . + PUBLIC SECTION. + METHODS constructor. + + PROTECTED SECTION. + METHODS inspect_tokens REDEFINITION. + + PRIVATE SECTION. + METHODS get_act_and_exp IMPORTING statement TYPE sstmnt + EXPORTING act TYPE stokesx + exp TYPE stokesx. + + METHODS is_variable IMPORTING token TYPE stokesx + RETURNING VALUE(result) TYPE abap_bool. + +ENDCLASS. + + +CLASS y_check_unit_test_assert IMPLEMENTATION. + + + METHOD constructor. + super->constructor( ). + + settings-pseudo_comment = '"#EC UT_ASSERT' ##NO_TEXT. + settings-disable_threshold_selection = abap_true. + settings-apply_on_productive_code = abap_false. + settings-apply_on_test_code = abap_true. + settings-threshold = 0. + settings-documentation = |{ c_docs_path-checks }unit_test_assert.md|. + + relevant_statement_types = VALUE #( ( scan_struc_stmnt_type-method ) ). + relevant_structure_types = VALUE #( ). + + set_check_message( 'Invalid Unit Test Assertion!' ). + ENDMETHOD. + + + METHOD inspect_tokens. + CHECK get_token_abs( statement-from ) CP 'CL_ABAP_UNIT_ASSERT=>ASSERT*' + OR get_token_abs( statement-from ) CP 'CL_AUNIT_ASSERT=>ASSERT*'. + + get_act_and_exp( EXPORTING statement = statement + IMPORTING act = DATA(act) + exp = DATA(exp) ). + + IF act IS INITIAL + OR exp IS INITIAL. + RETURN. + ENDIF. + + IF act-str <> exp-str. + IF is_variable( act ) = abap_true + OR is_variable( exp ) = abap_true. + RETURN. + ENDIF. + ENDIF. + + DATA(check_configuration) = detect_check_configuration( statement ). + + IF check_configuration IS INITIAL. + RETURN. + ENDIF. + + raise_error( statement_level = statement-level + statement_index = index + statement_from = statement-from + error_priority = check_configuration-prio ). + ENDMETHOD. + + + METHOD get_act_and_exp. + LOOP AT ref_scan_manager->tokens ASSIGNING FIELD-SYMBOL() + FROM statement-from TO statement-to. + DATA(tabix) = sy-tabix. + CASE -str. + WHEN 'ACT'. + act = ref_scan_manager->tokens[ tabix + 2 ]. + WHEN 'EXP'. + exp = ref_scan_manager->tokens[ tabix + 2 ]. + WHEN OTHERS. + CONTINUE. + ENDCASE. + ENDLOOP. + ENDMETHOD. + + + METHOD is_variable. + result = COND #( WHEN token-type = scan_token_type-literal THEN abap_false + WHEN token-type = scan_token_type-identifier THEN xsdbool( token-str CN '0123456789' ) ). + ENDMETHOD. + + +ENDCLASS. diff --git a/src/checks/y_check_unit_test_assert.clas.testclasses.abap b/src/checks/y_check_unit_test_assert.clas.testclasses.abap new file mode 100644 index 00000000..15fd6890 --- /dev/null +++ b/src/checks/y_check_unit_test_assert.clas.testclasses.abap @@ -0,0 +1,307 @@ +CLASS ltc_same_variable DEFINITION INHERITING FROM y_unit_test_base FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. + PROTECTED SECTION. + METHODS get_cut REDEFINITION. + METHODS get_code_with_issue REDEFINITION. + METHODS get_code_without_issue REDEFINITION. + METHODS get_code_with_exemption REDEFINITION. +ENDCLASS. + +CLASS ltc_same_variable IMPLEMENTATION. + + METHOD get_cut. + result ?= NEW y_check_unit_test_assert( ). + ENDMETHOD. + + METHOD get_code_with_issue. + result = VALUE #( + ( ' REPORT y_example. ' ) + + ( ' CLASS y_example DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' METHODS sum FOR TESTING. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD sum. ' ) + ( ' " given ' ) + ( ' DATA(first) = 10. ' ) + ( ' DATA(second) = 10. ' ) + ( ' " when ' ) + ( ' DATA(sum) = first + second. ' ) + ( ' " then ' ) + ( ' cl_abap_unit_assert=>assert_equals( act = sum ' ) + ( ' exp = sum ). ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + + METHOD get_code_without_issue. + result = VALUE #( + ( ' REPORT y_example. ' ) + + ( ' CLASS y_example DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' METHODS sum FOR TESTING. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD sum. ' ) + ( ' " given ' ) + ( ' DATA(first) = 10. ' ) + ( ' DATA(second) = 10. ' ) + ( ' " when ' ) + ( ' DATA(sum) = first + second. ' ) + ( ' " then ' ) + ( ' cl_abap_unit_assert=>assert_equals( act = sum ' ) + ( ' exp = 20 ). ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + + METHOD get_code_with_exemption. + result = VALUE #( + ( ' REPORT y_example. ' ) + + ( ' CLASS y_example DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' METHODS sum FOR TESTING. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD sum. ' ) + ( ' " given ' ) + ( ' DATA(first) = 10. ' ) + ( ' DATA(second) = 10. ' ) + ( ' " when ' ) + ( ' DATA(sum) = first + second. ' ) + ( ' " then ' ) + ( ' cl_abap_unit_assert=>assert_equals( act = sum ' ) + ( ' exp = sum ). "#EC UT_ASSERT ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + +ENDCLASS. + + +CLASS ltc_hardcoded_number DEFINITION INHERITING FROM y_unit_test_base FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. + PROTECTED SECTION. + METHODS get_cut REDEFINITION. + METHODS get_code_with_issue REDEFINITION. + METHODS get_code_without_issue REDEFINITION. + METHODS get_code_with_exemption REDEFINITION. +ENDCLASS. + +CLASS ltc_hardcoded_number IMPLEMENTATION. + + METHOD get_cut. + result ?= NEW y_check_unit_test_assert( ). + ENDMETHOD. + + METHOD get_code_with_issue. + result = VALUE #( + ( ' REPORT y_example. ' ) + + ( ' CLASS y_example DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' METHODS sum FOR TESTING. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD sum. ' ) + ( ' " given ' ) + ( ' DATA(first) = 10. ' ) + ( ' DATA(second) = 10. ' ) + ( ' " when ' ) + ( ' DATA(sum) = first + second. ' ) + ( ' " then ' ) + ( ' cl_aunit_assert=>assert_differs( act = 10 ' ) + ( ' exp = 20 ). ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + + METHOD get_code_without_issue. + result = VALUE #( + ( ' REPORT y_example. ' ) + + ( ' CLASS y_example DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' METHODS sum FOR TESTING. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD sum. ' ) + ( ' " given ' ) + ( ' DATA(first) = 10. ' ) + ( ' DATA(second) = 10. ' ) + ( ' " when ' ) + ( ' DATA(sum) = first + second. ' ) + ( ' " then ' ) + ( ' cl_aunit_assert=>assert_differs( act = 10 ' ) + ( ' exp = sum ). ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + + METHOD get_code_with_exemption. + result = VALUE #( + ( ' REPORT y_example. ' ) + + ( ' CLASS y_example DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' METHODS sum FOR TESTING. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD sum. ' ) + ( ' " given ' ) + ( ' DATA(first) = 10. ' ) + ( ' DATA(second) = 10. ' ) + ( ' " when ' ) + ( ' DATA(sum) = first + second. ' ) + ( ' " then ' ) + ( ' cl_aunit_assert=>assert_differs( act = 10 ' ) + ( ' exp = 20 ). "#EC UT_ASSERT ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + +ENDCLASS. + + +CLASS ltc_hardcoded_string DEFINITION INHERITING FROM y_unit_test_base FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. + PROTECTED SECTION. + METHODS get_cut REDEFINITION. + METHODS get_code_with_issue REDEFINITION. + METHODS get_code_without_issue REDEFINITION. + METHODS get_code_with_exemption REDEFINITION. +ENDCLASS. + +CLASS ltc_hardcoded_string IMPLEMENTATION. + + METHOD get_cut. + result ?= NEW y_check_unit_test_assert( ). + ENDMETHOD. + + METHOD get_code_with_issue. + result = VALUE #( + ( ' REPORT y_example. ' ) + + ( ' CLASS y_example DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' METHODS example FOR TESTING. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD example. ' ) + ( | cl_aunit_assert=>assert_differs( act = 'A' | ) + ( | exp = 'B' ). | ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + + METHOD get_code_without_issue. + result = VALUE #( + ( ' REPORT y_example. ' ) + + ( ' CLASS y_example DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' METHODS example FOR TESTING. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD example. ' ) + ( ' DATA(system) = sy-sysid. ' ) + ( | cl_aunit_assert=>assert_differs( act = system | ) + ( | exp = 'B' ). | ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + + METHOD get_code_with_exemption. + result = VALUE #( + ( ' REPORT y_example. ' ) + + ( ' CLASS y_example DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' METHODS example FOR TESTING. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD example. ' ) + ( | cl_aunit_assert=>assert_differs( act = 'A' | ) + ( | exp = 'B' ). "#EC UT_ASSERT | ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + +ENDCLASS. + + +CLASS ltc_public_classdata DEFINITION INHERITING FROM ltc_hardcoded_string FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. + PROTECTED SECTION. + METHODS get_code_without_issue REDEFINITION. +ENDCLASS. + +CLASS ltc_public_classdata IMPLEMENTATION. + + METHOD get_code_without_issue. + result = VALUE #( + ( ' REPORT y_example. ' ) + + ( ' CLASS y_example DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' METHODS example FOR TESTING. ' ) + ( ' CLASS-DATA system TYPE string. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD example. ' ) + ( | cl_aunit_assert=>assert_differs( act = system | ) + ( | exp = 'B' ). | ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + +ENDCLASS. + + +CLASS ltc_protected_data DEFINITION INHERITING FROM ltc_hardcoded_string FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. + PROTECTED SECTION. + METHODS get_code_without_issue REDEFINITION. +ENDCLASS. + +CLASS ltc_protected_data IMPLEMENTATION. + + METHOD get_code_without_issue. + result = VALUE #( + ( ' REPORT y_example. ' ) + + ( ' CLASS y_example DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' METHODS example FOR TESTING. ' ) + ( ' PROTECTED SECTION. ' ) + ( ' DATA system TYPE string. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD example. ' ) + ( | cl_aunit_assert=>assert_differs( act = system | ) + ( | exp = 'system' ). | ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + +ENDCLASS. diff --git a/src/checks/y_check_unit_test_assert.clas.xml b/src/checks/y_check_unit_test_assert.clas.xml new file mode 100644 index 00000000..394c176d --- /dev/null +++ b/src/checks/y_check_unit_test_assert.clas.xml @@ -0,0 +1,17 @@ + + + + + + Y_CHECK_UNIT_TEST_ASSERT + E + Unit Test Assert Validator + 1 + X + X + X + X + + + + diff --git a/src/examples/y_demo_failures.clas.testclasses.abap b/src/examples/y_demo_failures.clas.testclasses.abap index 7bc29e73..e0734b39 100644 --- a/src/examples/y_demo_failures.clas.testclasses.abap +++ b/src/examples/y_demo_failures.clas.testclasses.abap @@ -3,6 +3,7 @@ CLASS local_test_class DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT PRIVATE SECTION. METHODS db_access_in_ut FOR TESTING. METHODS external_call_in_ut FOR TESTING. + METHODS unit_test_assert FOR TESTING. ENDCLASS. CLASS local_test_class IMPLEMENTATION. @@ -10,10 +11,24 @@ CLASS local_test_class IMPLEMENTATION. METHOD db_access_in_ut. SELECT SINGLE * FROM t100 INTO @DATA(entry). + + cl_abap_unit_assert=>assert_not_initial( entry ). ENDMETHOD. METHOD external_call_in_ut. DATA alv TYPE REF TO cl_gui_alv_grid. + cl_abap_unit_assert=>fail( 'No Gui Allowed' ). + ENDMETHOD. + + METHOD unit_test_assert. + "Given + DATA(first) = 10. + DATA(second) = 20. + "When + DATA(sum) = first + second. + "Then + cl_abap_unit_assert=>assert_equals( act = sum + exp = sum ). ENDMETHOD. ENDCLASS.