Skip to content

Feature/unit test missing assert #629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/check_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
60 changes: 60 additions & 0 deletions docs/checks/unit_test_missing_assert.md
Original file line number Diff line number Diff line change
@@ -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)




114 changes: 114 additions & 0 deletions src/checks/y_check_missing_ut_assert.clas.abap
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
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( ).

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|.
settings-pseudo_comment = '"#EC CI_MISS_ASSERT'.

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(<token>)
FROM token_index.
IF to_upper( <token>-str ) = 'ENDMETHOD'.
EXIT.
ENDIF.

IF to_upper( <token>-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(<token>) WHERE str = 'METHODS'.
" Safeguarding boundaries
IF lines( ref_scan->tokens ) < sy-tabix + 3.
CONTINUE.
ENDIF.

IF ( to_upper( <token>-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.
64 changes: 64 additions & 0 deletions src/checks/y_check_missing_ut_assert.clas.testclasses.abap
Original file line number Diff line number Diff line change
@@ -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.' )
( ' ENDCLASS. ' )

( ' CLASS y_example_class IMPLEMENTATION. ' )
( ' METHOD test. "#EC CI_MISS_ASSERT ' )
( ' RETURN. ' )
( ' ENDMETHOD. ' )
( ' ENDCLASS. ' ) ).
ENDMETHOD.

ENDCLASS.
24 changes: 24 additions & 0 deletions src/checks/y_check_missing_ut_assert.clas.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?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_MISSING_UT_ASSERT</CLSNAME>
<LANGU>E</LANGU>
<DESCRIPT>Missing CL_ABAP_UNIT_ASSERT usage at test method</DESCRIPT>
<STATE>1</STATE>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
<WITH_UNIT_TESTS>X</WITH_UNIT_TESTS>
</VSEOCLASS>
<DESCRIPTIONS>
<SEOCOMPOTX>
<CMPNAME>IS_TESTING_METHOD</CMPNAME>
<LANGU>E</LANGU>
<DESCRIPT>Check if given method is marked FOR TESTING</DESCRIPT>
</SEOCOMPOTX>
</DESCRIPTIONS>
</asx:values>
</asx:abap>
</abapGit>