diff --git a/changelog.txt b/changelog.txt index c9c9d795..6f452f09 100644 --- a/changelog.txt +++ b/changelog.txt @@ -12,6 +12,11 @@ Upgrade Note ------------------ Whenever you upgrade code pal for ABAP, it is highly recommended to execute the Y_CI_CHECK_REGISTRATION report to activate/reactivate the Checks (SE38 transaction) and regenerate the respective Code Inspector Variant (SCI transaction) +2021-05-XX v.1.15.0 +------------------ ++ Name the code under test to CUT (#369) +* CALL METHOD Usage does not check if the method call is dynamic (#396) + 2021-05-03 v.1.14.1 ------------------ + Assert Classes (#378) diff --git a/docs/check_documentation.md b/docs/check_documentation.md index 07dad1c5..1b137f6f 100644 --- a/docs/check_documentation.md +++ b/docs/check_documentation.md @@ -14,6 +14,7 @@ - [Comment Type](checks/comment-type.md) - [Comment Usage](checks/comment-usage.md) - [Constants Interface](checks/constants-interface.md) +- [`cut` as Default](checks/cut-as-default.md) - [Cyclomatic Complexity](checks/cyclomatic-complexity.md) - [CX_ROOT Usage](checks/cx-root-usage.md) - [Database Access in Unit-Test](checks/db-access-in-ut.md) diff --git a/docs/checks/cut-as-default.md b/docs/checks/cut-as-default.md new file mode 100644 index 00000000..b16f486f --- /dev/null +++ b/docs/checks/cut-as-default.md @@ -0,0 +1,64 @@ +[code pal for ABAP](../../README.md) > [Documentation](../check_documentation.md) > [`cut` as Default](cut-as-default.md) + +## `cut` as Default + +### What is the Intent of the Check? + +In a test class, the code under the test can be represented using a meaningful name, or `cut` as a default. +If you decide to follow `cut` as a default, this Check reports test methods that do not follow the pattern. + +### How does the check work? + +For each `for testing` method, it searches for the `cut` on it, and also on its `class definition`. + +### How to solve the issue? + +Name the code under the test to `cut`. + +### What to do in case of exception? + +If you found a meaningful name, you can suppress this finding by using the pseudo comment `"#EC CUT_AS_DEFAULT` which has to be placed after the method implementation statement: + +```abap +METHOD test. "#EC CUT_AS_DEFAULT + " given + DATA demo_failures TYPE REF TO y_demo_failures. + " when + demo_failures = NEW #( ). + " then + cl_abap_unit_assert=>assert_bound( demo_failures ). +ENDMETHOD. +``` + +### Example + +Before the check: + + +```abap +METHOD test. + DATA class_abc TYPE REF TO ... + DATA class_123 TYPE REF TO ... + DATA class_qwe TYPE REF TO ... + ... + cl_abap_unit_assert=>assert_bound( class_qwe ). +ENDMETHOD. +``` + +After the check: + +```abap +METHOD test. + DATA class_abc TYPE REF TO ... + DATA class_123 TYPE REF TO ... + DATA cut TYPE REF TO ... + ... + cl_abap_unit_assert=>assert_bound( cut ). +ENDMETHOD. +``` + +### Further Readings & Knowledge + +* [Clean ABAP: Name the code under test meaningfully, or default to CUT](https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#name-the-code-under-test-meaningfully-or-default-to-cut) + + diff --git a/src/checks/y_check_call_method_usage.clas.abap b/src/checks/y_check_call_method_usage.clas.abap index b00a6e8d..edfb224b 100644 --- a/src/checks/y_check_call_method_usage.clas.abap +++ b/src/checks/y_check_call_method_usage.clas.abap @@ -32,7 +32,8 @@ CLASS y_check_call_method_usage IMPLEMENTATION. DATA(is_dynamic) = xsdbool( token CP '*->(*)*' OR token CP '*=>(*)*' OR token CP '*)=>(*)*' - OR token CP '*)=>*' ). + OR token CP '*)=>*' + OR token CP '(*)' ). DATA(check_configuration) = detect_check_configuration( statement ). diff --git a/src/checks/y_check_call_method_usage.clas.testclasses.abap b/src/checks/y_check_call_method_usage.clas.testclasses.abap index fe2705cc..8c802f18 100644 --- a/src/checks/y_check_call_method_usage.clas.testclasses.abap +++ b/src/checks/y_check_call_method_usage.clas.testclasses.abap @@ -20,7 +20,6 @@ CLASS ltc_class IMPLEMENTATION. ( ' PUBLIC SECTION. ' ) ( ' METHODS class_method. ' ) ( ' CLASS-METHODS execute. ' ) - ( ' PROTECTED SECTION. ' ) ( 'ENDCLASS. ' ) ( 'CLASS lcl_classname IMPLEMENTATION. ' ) @@ -41,12 +40,12 @@ CLASS ltc_class IMPLEMENTATION. ( ' PUBLIC SECTION. ' ) ( ' METHODS class_method. ' ) ( ' CLASS-METHODS execute. ' ) - ( ' PROTECTED SECTION. ' ) ( 'ENDCLASS. ' ) ( 'CLASS lcl_classname IMPLEMENTATION. ' ) ( ' METHOD class_method. ' ) ( ' lcl_classname=>execute( ). ' ) + ( | CALL METHOD ('execute'). | ) ( ' ENDMETHOD. ' ) ( ' METHOD execute. ' ) ( ' ENDMETHOD. ' ) @@ -62,7 +61,6 @@ CLASS ltc_class IMPLEMENTATION. ( ' PUBLIC SECTION. ' ) ( ' METHODS class_method. ' ) ( ' CLASS-METHODS execute. ' ) - ( ' PROTECTED SECTION. ' ) ( 'ENDCLASS. ' ) ( 'CLASS lcl_classname IMPLEMENTATION. ' ) @@ -98,7 +96,6 @@ CLASS ltc_report IMPLEMENTATION. ( 'CLASS lcl_classname DEFINITION. ' ) ( ' PUBLIC SECTION. ' ) ( ' CLASS-METHODS execute. ' ) - ( ' PROTECTED SECTION. ' ) ( 'ENDCLASS. ' ) ( 'CLASS lcl_classname IMPLEMENTATION. ' ) @@ -120,7 +117,6 @@ CLASS ltc_report IMPLEMENTATION. ( 'CLASS lcl_classname DEFINITION. ' ) ( ' PUBLIC SECTION. ' ) ( ' CLASS-METHODS execute. ' ) - ( ' PROTECTED SECTION. ' ) ( 'ENDCLASS. ' ) ( 'CLASS lcl_classname IMPLEMENTATION. ' ) diff --git a/src/checks/y_check_cut_as_default.clas.abap b/src/checks/y_check_cut_as_default.clas.abap new file mode 100644 index 00000000..054f5fe9 --- /dev/null +++ b/src/checks/y_check_cut_as_default.clas.abap @@ -0,0 +1,119 @@ +CLASS y_check_cut_as_default DEFINITION PUBLIC INHERITING FROM y_check_base CREATE PUBLIC . + PUBLIC SECTION. + METHODS constructor. + + PROTECTED SECTION. + METHODS inspect_tokens REDEFINITION. + + PRIVATE SECTION. + METHODS is_for_testing IMPORTING method_name TYPE string + class_definition TYPE sstruc + RETURNING VALUE(result) TYPE abap_bool. + + METHODS has_cut IMPORTING structure TYPE sstruc + RETURNING VALUE(result) TYPE abap_bool. + + METHODS get_class_definition IMPORTING structure TYPE sstruc + RETURNING VALUE(result) TYPE sstruc + RAISING cx_sy_itab_line_not_found. + +ENDCLASS. + + +CLASS y_check_cut_as_default IMPLEMENTATION. + + + METHOD constructor. + super->constructor( ). + + settings-pseudo_comment = '"#EC CUT_AS_DEFAULT' ##NO_TEXT. + settings-disable_threshold_selection = abap_true. + settings-disable_on_testcode_selection = abap_true. + settings-disable_on_prodcode_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 }cut-as-default.md|. + relevant_statement_types = VALUE #( ( scan_struc_stmnt_type-method ) ). + relevant_structure_types = VALUE #( ). + + set_check_message( 'Name the code under test to `cut`' ). + ENDMETHOD. + + + METHOD inspect_tokens. + CHECK get_token_abs( statement-from ) = 'METHOD'. + + DATA(class_definition) = get_class_definition( structure ). + + DATA(for_testing) = is_for_testing( method_name = get_token_abs( statement-from + 1 ) + class_definition = class_definition ). + + IF for_testing = abap_false. + RETURN. + ENDIF. + + IF has_cut( structure ) = abap_true + OR has_cut( class_definition ) = abap_true. + RETURN. + 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 is_for_testing. + LOOP AT ref_scan_manager->statements ASSIGNING FIELD-SYMBOL(<statement>) + FROM class_definition-stmnt_from TO class_definition-stmnt_to. + + IF get_token_abs( <statement>-from ) <> 'METHODS' + AND get_token_abs( <statement>-from ) <> 'CLASS-METHODS'. + CONTINUE. + ENDIF. + + DATA(statement_abs) = get_statement_abs( <statement> ). + + IF statement_abs NP |*{ method_name }*|. + CONTINUE. + ENDIF. + + result = xsdbool( statement_abs CP '*FOR TESTING*' ). + RETURN. + + ENDLOOP. + ENDMETHOD. + + + METHOD has_cut. + LOOP AT ref_scan_manager->statements ASSIGNING FIELD-SYMBOL(<statement>) + FROM structure-stmnt_from TO structure-stmnt_to + WHERE type <> scan_stmnt_type-comment + AND type <> scan_stmnt_type-pragma. + DATA(statement_abs) = get_statement_abs( <statement> ). + + result = xsdbool( statement_abs CP '* CUT *' + OR statement_abs CP '*_CUT *' ). + + IF result = abap_true. + RETURN. + ENDIF. + ENDLOOP. + ENDMETHOD. + + + METHOD get_class_definition. + DATA(class_implementation) = ref_scan_manager->structures[ structure-back ]. + result = ref_scan_manager->structures[ class_implementation-back ]. + ENDMETHOD. + + +ENDCLASS. diff --git a/src/checks/y_check_cut_as_default.clas.testclasses.abap b/src/checks/y_check_cut_as_default.clas.testclasses.abap new file mode 100644 index 00000000..a8b6e3be --- /dev/null +++ b/src/checks/y_check_cut_as_default.clas.testclasses.abap @@ -0,0 +1,216 @@ +CLASS ltc_cut 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_cut IMPLEMENTATION. + + METHOD get_cut. + result ?= NEW y_check_cut_as_default( ). + 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. ' ) + ( ' " given ' ) + ( ' DATA demo_failures TYPE REF TO y_demo_failures. ' ) + ( ' " when ' ) + ( ' demo_failures = NEW #( ). ' ) + ( ' " then ' ) + ( ' cl_abap_unit_assert=>assert_bound( demo_failures ). ' ) + ( ' 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. ' ) + ( ' " given ' ) + ( ' DATA cut TYPE REF TO y_demo_failures. ' ) + ( ' " when ' ) + ( ' cut = NEW #( ). ' ) + ( ' " then ' ) + ( ' cl_abap_unit_assert=>assert_bound( cut ). ' ) + ( ' 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. "#EC CUT_AS_DEFAULT ' ) + ( ' " given ' ) + ( ' DATA demo_failures TYPE REF TO y_demo_failures. ' ) + ( ' " when ' ) + ( ' demo_failures = NEW #( ). ' ) + ( ' " then ' ) + ( ' cl_abap_unit_assert=>assert_bound( demo_failures ). ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + +ENDCLASS. + + + +CLASS ltc_prefix DEFINITION INHERITING FROM ltc_cut FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. + PROTECTED SECTION. + METHODS get_code_without_issue REDEFINITION. +ENDCLASS. + +CLASS ltc_prefix 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. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD example. ' ) + ( ' " given ' ) + ( ' DATA lo_cut TYPE REF TO y_demo_failures. ' ) + ( ' " when ' ) + ( ' lo_cut = NEW #( ). ' ) + ( ' " then ' ) + ( ' cl_abap_unit_assert=>assert_bound( lo_cut ). ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + +ENDCLASS. + + + +CLASS ltc_not_for_testing DEFINITION INHERITING FROM ltc_cut FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. + PROTECTED SECTION. + METHODS get_code_without_issue REDEFINITION. +ENDCLASS. + +CLASS ltc_not_for_testing 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. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD example. ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + +ENDCLASS. + + + +CLASS ltc_attribute DEFINITION INHERITING FROM ltc_cut FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. + PROTECTED SECTION. + METHODS get_code_without_issue REDEFINITION. +ENDCLASS. + +CLASS ltc_attribute 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. ' ) + ( ' PRIVATE SECTION. ' ) + ( ' DATA lo_cut TYPE REF TO y_demo_failures. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD example. ' ) + ( ' " when ' ) + ( ' lo_cut = NEW #( ). ' ) + ( ' " then ' ) + ( ' cl_abap_unit_assert=>assert_bound( lo_cut ). ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + +ENDCLASS. + + + +CLASS ltc_cut_out_of_testing_method DEFINITION INHERITING FROM ltc_cut FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. + PROTECTED SECTION. + METHODS get_code_without_issue REDEFINITION. +ENDCLASS. + +CLASS ltc_cut_out_of_testing_method 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. ' ) + ( ' METHODS when. ' ) + ( ' METHODS then. ' ) + ( ' PRIVATE SECTION. ' ) + ( ' DATA cut TYPE REF TO y_demo_failures. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example IMPLEMENTATION. ' ) + ( ' METHOD example. ' ) + ( ' when( ).' ) + ( ' then( ).' ) + ( ' ENDMETHOD. ' ) + + ( ' METHOD when. ' ) + ( ' cut = NEW #( ). ' ) + ( ' ENDMETHOD. ' ) + + ( ' METHOD then. ' ) + ( ' cl_abap_unit_assert=>assert_bound( cut ). ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) + ). + ENDMETHOD. + +ENDCLASS. diff --git a/src/checks/y_check_cut_as_default.clas.xml b/src/checks/y_check_cut_as_default.clas.xml new file mode 100644 index 00000000..775dd2bb --- /dev/null +++ b/src/checks/y_check_cut_as_default.clas.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0"> + <asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0"> + <asx:values> + <VSEOCLASS> + <CLSNAME>Y_CHECK_CUT_AS_DEFAULT</CLSNAME> + <LANGU>E</LANGU> + <DESCRIPT>Name the Code Under Test to `cut`</DESCRIPT> + <STATE>1</STATE> + <CLSCCINCL>X</CLSCCINCL> + <FIXPT>X</FIXPT> + <UNICODE>X</UNICODE> + <WITH_UNIT_TESTS>X</WITH_UNIT_TESTS> + </VSEOCLASS> + </asx:values> + </asx:abap> +</abapGit> diff --git a/src/checks/y_check_unit_test_assert.clas.abap b/src/checks/y_check_unit_test_assert.clas.abap index 94341eb1..b92fa6ce 100644 --- a/src/checks/y_check_unit_test_assert.clas.abap +++ b/src/checks/y_check_unit_test_assert.clas.abap @@ -25,6 +25,8 @@ CLASS y_check_unit_test_assert IMPLEMENTATION. settings-pseudo_comment = '"#EC UT_ASSERT' ##NO_TEXT. settings-disable_threshold_selection = abap_true. + settings-disable_on_testcode_selection = abap_true. + settings-disable_on_prodcode_selection = abap_true. settings-apply_on_productive_code = abap_false. settings-apply_on_test_code = abap_true. settings-threshold = 0. diff --git a/src/foundation/y_check_base.clas.abap b/src/foundation/y_check_base.clas.abap index 3307562f..51a64a47 100644 --- a/src/foundation/y_check_base.clas.abap +++ b/src/foundation/y_check_base.clas.abap @@ -105,6 +105,8 @@ CLASS y_check_base DEFINITION PUBLIC ABSTRACT is_include_specific TYPE sci_inclspec DEFAULT ' ' additional_information TYPE xstring OPTIONAL checksum TYPE int4 OPTIONAL. "#EC OPTL_PARAM + METHODS get_statement_abs IMPORTING statement TYPE sstmnt + RETURNING VALUE(result) TYPE string. METHODS get_column_abs REDEFINITION. METHODS get_column_rel REDEFINITION. @@ -819,4 +821,16 @@ CLASS Y_CHECK_BASE IMPLEMENTATION. METHOD switch_bool. result = xsdbool( boolean = abap_false ). ENDMETHOD. + + + METHOD get_statement_abs. + LOOP AT ref_scan_manager->tokens ASSIGNING FIELD-SYMBOL(<token>) + FROM statement-from TO statement-to. + result = COND #( WHEN result IS INITIAL THEN <token>-str + ELSE |{ result } { <token>-str }| ). + + ENDLOOP. + ENDMETHOD. + + ENDCLASS. diff --git a/src/y_code_pal_version.intf.abap b/src/y_code_pal_version.intf.abap index dd2848a6..c38b3827 100644 --- a/src/y_code_pal_version.intf.abap +++ b/src/y_code_pal_version.intf.abap @@ -1,3 +1,3 @@ INTERFACE y_code_pal_version PUBLIC. - CONSTANTS abap TYPE string VALUE '1.14.1' ##NO_TEXT. + CONSTANTS abap TYPE string VALUE '1.15.0' ##NO_TEXT. ENDINTERFACE.