Skip to content

Unit Test Assert Check #347

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

Merged
merged 12 commits into from
Apr 6, 2021
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions docs/check_documentation.md
Original file line number Diff line number Diff line change
@@ -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)
59 changes: 59 additions & 0 deletions docs/checks/unit_test_assert.md
Original file line number Diff line number Diff line change
@@ -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.
```
94 changes: 94 additions & 0 deletions src/checks/y_check_unit_test_assert.clas.abap
Original file line number Diff line number Diff line change
@@ -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(<token>)
FROM statement-from TO statement-to.
DATA(tabix) = sy-tabix.
CASE <token>-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.
307 changes: 307 additions & 0 deletions src/checks/y_check_unit_test_assert.clas.testclasses.abap
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 17 additions & 0 deletions src/checks/y_check_unit_test_assert.clas.xml
Original file line number Diff line number Diff line change
@@ -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_UNIT_TEST_ASSERT</CLSNAME>
<LANGU>E</LANGU>
<DESCRIPT>Unit Test Assert Validator</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>
15 changes: 15 additions & 0 deletions src/examples/y_demo_failures.clas.testclasses.abap
Original file line number Diff line number Diff line change
@@ -3,17 +3,32 @@ 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.

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.