From ee850267d8000456185be3061d41fdde0b569bbc Mon Sep 17 00:00:00 2001 From: gardian12 Date: Fri, 25 Jul 2025 09:51:08 +0200 Subject: [PATCH 1/4] added new check "unit test missing assert" --- docs/check_documentation.md | 1 + docs/checks/unit_test_missing_assert.md | 60 +++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 docs/checks/unit_test_missing_assert.md diff --git a/docs/check_documentation.md b/docs/check_documentation.md index b43627a6..c1a0bd97 100644 --- a/docs/check_documentation.md +++ b/docs/check_documentation.md @@ -61,3 +61,4 @@ - [Text Assembly](checks/text-assembly.md) - [Unit-Test Coverages (Statement, Branch and Procedure)](checks/unit-test-coverages.md) - [Unit-Test Assert Validator](checks/unit_test_assert.md) +- [Unit-Test Missing Assert](checks/unit_test_missing_assert.md) diff --git a/docs/checks/unit_test_missing_assert.md b/docs/checks/unit_test_missing_assert.md new file mode 100644 index 00000000..bd97803d --- /dev/null +++ b/docs/checks/unit_test_missing_assert.md @@ -0,0 +1,60 @@ +[code pal for ABAP](../../README.md) > [Documentation](../check_documentation.md) > [Unit-Test Missing Assert](unit_test_missing_assert.md) + +## Unit-Test Missing Assert + +### What is the intent of the check? + +This check identifies unit test methods that do not include any assertions while utilizing the `CL_ABAP_UNIT_ASSERT` class. A unit test that lacks any assertions or validation of expected behavior is fundamentally meaningless. + +### How does the check work? + +The check evaluates each unit test method to determine whether at least one occurrence of the `CL_ABAP_UNIT_ASSERT` class is present. A finding is raised if no static methods of `CL_ABAP_UNIT_ASSERT` have been implemented. + +### How to solve the issue? + +Please include a meaningful assertion using `ASSERT...`, or invoke `SKIP` or `FAIL` to automate a specific behavior of the test method. + +### What to do in case of exception? + +Currently, there is no pseudo comment available to disable the check. + +### Example + +Before the check: + +```abap +METHODS initialize FOR TESTING. + +... + + METHOD initialize. + + f_cut = NEW #( ). + + ENDMETHOD. +``` + +After the check: + +```abap +METHODS initialize FOR TESTING. + +... + + METHOD initialize. + f_cut = NEW #( ). + + cl_abap_unit_assert=>assert_bound( + act = f_cut + msg = 'Instance was not created. Why?' ). + ENDMETHOD. +``` + +### Further Readings & Knowledge + +* [Clean ABAP: Don't obsess about coverage](https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#dont-obsess-about-coverage) +* [Unit testing with ABAP unit](https://help.sap.com/docs/SAP_S4HANA_CLOUD/25cf71e63940453397a32dc2b7676947/08c60b52cb85444ea3069779274b43db.html?q=abap%20unit%20test) + + + + From d894a2594f5c73239ee0b74bd7f59cc776aa1a52 Mon Sep 17 00:00:00 2001 From: gardian12 Date: Fri, 25 Jul 2025 08:00:50 +0000 Subject: [PATCH 2/4] added new check "unit test missing assert" - impl --- .../y_check_missing_ut_assert.clas.abap | 115 ++++++++++++++++++ ...ck_missing_ut_assert.clas.testclasses.abap | 64 ++++++++++ src/checks/y_check_missing_ut_assert.clas.xml | 24 ++++ 3 files changed, 203 insertions(+) create mode 100644 src/checks/y_check_missing_ut_assert.clas.abap create mode 100644 src/checks/y_check_missing_ut_assert.clas.testclasses.abap create mode 100644 src/checks/y_check_missing_ut_assert.clas.xml diff --git a/src/checks/y_check_missing_ut_assert.clas.abap b/src/checks/y_check_missing_ut_assert.clas.abap new file mode 100644 index 00000000..658fe1cb --- /dev/null +++ b/src/checks/y_check_missing_ut_assert.clas.abap @@ -0,0 +1,115 @@ +CLASS y_check_missing_ut_assert DEFINITION + PUBLIC + INHERITING FROM y_check_base + CREATE PUBLIC. + + PUBLIC SECTION. + + METHODS constructor. + + PROTECTED SECTION. + METHODS inspect_tokens REDEFINITION. + + PRIVATE SECTION. + + METHODS is_testing_method + IMPORTING method_name TYPE string + RETURNING VALUE(is_testing_method) TYPE abap_bool. +ENDCLASS. + + +CLASS y_check_missing_ut_assert IMPLEMENTATION. + + METHOD constructor. + super->constructor( ). + + " As long as pseudo comment is not supported at unit test code, the option is deactivated +* settings-pseudo_comment = '"#EC MISS_UT_ASSERT'. + 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-prio = c_warning. + settings-documentation = |{ c_docs_path-checks }unit_test_missing_assert.md|. + + relevant_statement_types = VALUE #( ( scan_struc_stmnt_type-class_definition ) + ( scan_struc_stmnt_type-method ) ). + + relevant_structure_types = VALUE #( ). + + set_check_message( 'Missing assertion in unit test method "&1"' ). + ENDMETHOD. + + + METHOD inspect_tokens. + + " Check if method part of a class FOR TESTING + CHECK is_test_code( statement ). + + " Start at each method + CHECK get_token_abs( statement-from ) = 'METHOD'. + + " Check method on detail + DATA(token_index) = statement-from. + DATA(method_name) = get_token_abs( token_index + 1 ). + + " Check if method is marked as FOR TESTING + IF NOT is_testing_method( method_name ). + RETURN. + ENDIF. + + " Check inside method + LOOP AT ref_scan->tokens ASSIGNING FIELD-SYMBOL() + FROM token_index. + IF to_upper( -str ) = 'ENDMETHOD'. + EXIT. + ENDIF. + + IF to_upper( -str ) CP |CL_ABAP_UNIT_ASSERT=>*|. + " If at least one ASSERT was found, all fine we can leave here. + RETURN. + ENDIF. + token_index = token_index + 1. + ENDLOOP. + + " Up to here now valid coding was found, so finding has to be raised. + DATA(check_configuration) = detect_check_configuration( statement ). + + raise_error( statement_level = statement-level + statement_index = index + statement_from = statement-from + check_configuration = check_configuration + parameter_01 = |{ method_name }| ). + + ENDMETHOD. + + + METHOD is_testing_method. + + IF method_name IS INITIAL. + is_testing_method = abap_false. + RETURN. + ENDIF. + + LOOP AT ref_scan->tokens ASSIGNING FIELD-SYMBOL() WHERE str = 'METHODS'. + " Safeguarding boundaries + IF lines( ref_scan->tokens ) < sy-tabix + 3. + CONTINUE. + ENDIF. + + IF ( to_upper( -str ) = 'METHODS' ) + AND ( to_upper( ref_scan->tokens[ sy-tabix + 1 ]-str ) = method_name ) + AND ( to_upper( ref_scan->tokens[ sy-tabix + 2 ]-str ) = 'FOR' ) + AND ( to_upper( ref_scan->tokens[ sy-tabix + 3 ]-str ) = 'TESTING' ). + is_testing_method = abap_true. + RETURN. + ENDIF. + ENDLOOP. + + is_testing_method = abap_false. + + ENDMETHOD. + +ENDCLASS. diff --git a/src/checks/y_check_missing_ut_assert.clas.testclasses.abap b/src/checks/y_check_missing_ut_assert.clas.testclasses.abap new file mode 100644 index 00000000..08285d5f --- /dev/null +++ b/src/checks/y_check_missing_ut_assert.clas.testclasses.abap @@ -0,0 +1,64 @@ +CLASS local_test_class 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 local_test_class IMPLEMENTATION. + + METHOD get_cut. + result ?= NEW y_check_missing_ut_assert( ). + ENDMETHOD. + + + METHOD get_code_with_issue. + result = VALUE #( ( 'REPORT y_example. ' ) + ( ' CLASS y_example_class DEFINITION FOR TESTING. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' PROTECTED SECTION. ' ) + ( ' METHODS test FOR TESTING. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example_class IMPLEMENTATION. ' ) + ( ' METHOD test. ' ) + ( ' RETURN. ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) ). + ENDMETHOD. + + + METHOD get_code_without_issue. + result = VALUE #( ( 'REPORT y_example. ' ) + ( ' CLASS y_example_class DEFINITION FOR TESTING. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' PROTECTED SECTION. ' ) + ( ' METHODS test FOR TESTING. ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example_class IMPLEMENTATION. ' ) + ( ' METHOD test. ' ) + ( ' CL_ABAP_UNIT_ASSERT=>ASSERT_BOUND( act = me ). ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) ). + ENDMETHOD. + + + METHOD get_code_with_exemption. + result = VALUE #( ( 'REPORT y_example. ' ) + ( ' CLASS y_example_class DEFINITION FOR TESTING. ' ) + ( ' PUBLIC SECTION. ' ) + ( ' PROTECTED SECTION. ' ) + ( ' METHODS test FOR TESTING. "#EC MISS_UT_ASSERT ' ) + ( ' ENDCLASS. ' ) + + ( ' CLASS y_example_class IMPLEMENTATION. ' ) + ( ' METHOD test. ' ) + ( ' RETURN. ' ) + ( ' ENDMETHOD. ' ) + ( ' ENDCLASS. ' ) ). + ENDMETHOD. + +ENDCLASS. diff --git a/src/checks/y_check_missing_ut_assert.clas.xml b/src/checks/y_check_missing_ut_assert.clas.xml new file mode 100644 index 00000000..3dd8bedc --- /dev/null +++ b/src/checks/y_check_missing_ut_assert.clas.xml @@ -0,0 +1,24 @@ + + + + + + Y_CHECK_MISSING_UT_ASSERT + E + Missing CL_ABAP_UNIT_ASSERT usage at test method + 1 + X + X + X + X + + + + IS_TESTING_METHOD + E + Check if given method is marked FOR TESTING + + + + + From cbb52dd55b4e5df2f018e50f11ec670b771973f7 Mon Sep 17 00:00:00 2001 From: gardian12 Date: Fri, 25 Jul 2025 10:10:01 +0200 Subject: [PATCH 3/4] Update src/checks/y_check_missing_ut_assert.clas.abap removed comments Co-authored-by: abaplint[bot] <24845621+abaplint[bot]@users.noreply.github.com> --- src/checks/y_check_missing_ut_assert.clas.abap | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/checks/y_check_missing_ut_assert.clas.abap b/src/checks/y_check_missing_ut_assert.clas.abap index 658fe1cb..a2dd6378 100644 --- a/src/checks/y_check_missing_ut_assert.clas.abap +++ b/src/checks/y_check_missing_ut_assert.clas.abap @@ -23,8 +23,6 @@ CLASS y_check_missing_ut_assert IMPLEMENTATION. METHOD constructor. super->constructor( ). - " As long as pseudo comment is not supported at unit test code, the option is deactivated -* settings-pseudo_comment = '"#EC MISS_UT_ASSERT'. settings-disable_threshold_selection = abap_true. settings-disable_on_testcode_selection = abap_true. settings-disable_on_prodcode_selection = abap_true. From cc05e11d889f796a74702115d7122217825cb94e Mon Sep 17 00:00:00 2001 From: Bjoern Jueliger Date: Tue, 5 Aug 2025 08:55:35 +0000 Subject: [PATCH 4/4] Add pseudo comment The CodePal always looks for the pseudo comment at the position of the finding. Since we position the finding on the first line of the method implementation, the unit test also needs to position it at the first line, then it works. --- src/checks/y_check_missing_ut_assert.clas.abap | 1 + src/checks/y_check_missing_ut_assert.clas.testclasses.abap | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/checks/y_check_missing_ut_assert.clas.abap b/src/checks/y_check_missing_ut_assert.clas.abap index a2dd6378..d919bf02 100644 --- a/src/checks/y_check_missing_ut_assert.clas.abap +++ b/src/checks/y_check_missing_ut_assert.clas.abap @@ -31,6 +31,7 @@ CLASS y_check_missing_ut_assert IMPLEMENTATION. settings-threshold = 0. settings-prio = c_warning. settings-documentation = |{ c_docs_path-checks }unit_test_missing_assert.md|. + settings-pseudo_comment = '"#EC CI_MISS_ASSERT'. relevant_statement_types = VALUE #( ( scan_struc_stmnt_type-class_definition ) ( scan_struc_stmnt_type-method ) ). diff --git a/src/checks/y_check_missing_ut_assert.clas.testclasses.abap b/src/checks/y_check_missing_ut_assert.clas.testclasses.abap index 08285d5f..a71b4164 100644 --- a/src/checks/y_check_missing_ut_assert.clas.testclasses.abap +++ b/src/checks/y_check_missing_ut_assert.clas.testclasses.abap @@ -51,11 +51,11 @@ CLASS local_test_class IMPLEMENTATION. ( ' CLASS y_example_class DEFINITION FOR TESTING. ' ) ( ' PUBLIC SECTION. ' ) ( ' PROTECTED SECTION. ' ) - ( ' METHODS test FOR TESTING. "#EC MISS_UT_ASSERT ' ) + ( ' METHODS test FOR TESTING.' ) ( ' ENDCLASS. ' ) ( ' CLASS y_example_class IMPLEMENTATION. ' ) - ( ' METHOD test. ' ) + ( ' METHOD test. "#EC CI_MISS_ASSERT ' ) ( ' RETURN. ' ) ( ' ENDMETHOD. ' ) ( ' ENDCLASS. ' ) ).