Skip to content

Latest commit

 

History

History
225 lines (168 loc) · 8.82 KB

File metadata and controls

225 lines (168 loc) · 8.82 KB

<-- previous rule | overview | next rule -->

Use assert class instead of ASSERT

Replaces ASSERT statements (in product code) with static calls to an assert class to make the error cases testable, too.

Note that the class name must be adjusted to the respective application (CX_..._ASSERT). The class should at least implement the methods called in the example below.

References

Options

  • Assert class name: [cx_assert]
  • Unchain ASSERT: chains (required for processing them with this rule)

Examples

CLASS cl_product_code IMPLEMENTATION.
  METHOD use_assert_class.
    " Explanation: Using ASSERT in product code is discouraged, because it immediately dumps, even when the code
    " is called from a test. You can therefore only test the 'happy' cases. This is unsatisfactory, because for
    " code to be clean and testable, you should be able to safeguard that assertions work correctly in unhappy cases.
    
    " The solution is to not use ASSERT in product code, but to rather call a 'productive' assert class cx_XYZ_assert,
    " which has methods just like CL_ABAP_UNIT_ASSERT to check your expectations, but is specific to your solution XYZ.
    " With that, even the 'unhappy' cases can be tested, because your tests can catch cx_XYZ_assert exceptions and
    " ensure they are raised when they should.
    
    " Once you have created such a class (short example see below), this cleanup rule helps to automatically convert
    " existing ASSERT statements into static cx_XYZ_assert=>...( ) calls. Since the exact desired behavior of the
    " cx_XYZ_assert class may depend on your solution, the class name is not fixed, but can be configured above.
    " To exclude accidental use of this rule, this cleanup rule is intentionally deactivated at first, and the
    " class cx_assert does not exist, but must first be adjusted to your own implementation.

    ASSERT lo_instance IS BOUND.
    ASSERT is_any_structure-component IS NOT BOUND.

    ASSERT is_parameters-component_name IS INITIAL.
    ASSERT io_any_instance IS NOT INITIAL.

    ASSERT sy-subrc = 0. " item must exist
    ASSERT sy-subrc = 4.
    ASSERT sy-subrc = get_expected_subrc_value( param_a = 'abc' 
                                                param_b = 'def' ).

    ASSERT lv_was_initialized = abap_true.
    ASSERT mv_is_valid = abap_false.
    ASSERT line_exists( lts_table[ iv_param_a = 1 
                                   iv_param_b = 'abc' ] ) = abap_true.

    ASSERT ms_data-item_type = if_any_interface=>co_any_item_type.
    ASSERT lv_timestamp(7) = lts_table[ 1 ]-start_timestamp(7).

    ASSERT lo_any_item_instance->ms_data-item_category <> if_any_interface=>co_any_item_category.
    ASSERT sy-subrc <> 0.

    ASSERT 1 = 2.

    ASSERT <ls_any_field_symbol> IS ASSIGNED.
    ASSERT <ls_other_field_symbol> IS NOT ASSIGNED.
    ASSERT is_any_structure IS NOT INITIAL OR is_other_structure IS NOT INITIAL.
    ASSERT <ls_row>-item_key <= ms_parameters-last_item_key.
    ASSERT lv_quantity <= 100.
    ASSERT abs( <ls_any_field_symbol>-sum_quantity ) > 0.

    " chains can only be processed if they are first unchained
    ASSERT: sy-subrc = 0,
            io_instance IS BOUND,
            iv_is_valid = abap_false.
  ENDMETHOD.
ENDCLASS.

" example implementation of the assert class, using simplified CL_ABAP_UNIT_ASSERT method signatures:
CLASS cx_xyz_assert DEFINITION PUBLIC
      INHERITING FROM cx_no_check FINAL
      CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS constructor
      IMPORTING previous LIKE previous OPTIONAL.

    CLASS-METHODS assert_equals
      IMPORTING VALUE(act) TYPE any
                VALUE(exp) TYPE any.

    CLASS-METHODS assert_bound
      IMPORTING VALUE(act) TYPE any.

    " ...
ENDCLASS.

CLASS cx_xyz_assert IMPLEMENTATION.
  METHOD constructor.
    super->constructor( previous = previous ).
  ENDMETHOD.

  METHOD assert_equals.
    IF act <> exp.
      RAISE EXCEPTION NEW cx_xyz_assert( ).
    ENDIF.
  ENDMETHOD.

  METHOD assert_bound.
    IF act IS NOT BOUND.
      RAISE EXCEPTION NEW cx_xyz_assert( ).
    ENDIF.
  ENDMETHOD.

  " ...
ENDCLASS.

Resulting code:

CLASS cl_product_code IMPLEMENTATION.
  METHOD use_assert_class.
    " Explanation: Using ASSERT in product code is discouraged, because it immediately dumps, even when the code
    " is called from a test. You can therefore only test the 'happy' cases. This is unsatisfactory, because for
    " code to be clean and testable, you should be able to safeguard that assertions work correctly in unhappy cases.

    " The solution is to not use ASSERT in product code, but to rather call a 'productive' assert class cx_XYZ_assert,
    " which has methods just like CL_ABAP_UNIT_ASSERT to check your expectations, but is specific to your solution XYZ.
    " With that, even the 'unhappy' cases can be tested, because your tests can catch cx_XYZ_assert exceptions and
    " ensure they are raised when they should.

    " Once you have created such a class (short example see below), this cleanup rule helps to automatically convert
    " existing ASSERT statements into static cx_XYZ_assert=>...( ) calls. Since the exact desired behavior of the
    " cx_XYZ_assert class may depend on your solution, the class name is not fixed, but can be configured above.
    " To exclude accidental use of this rule, this cleanup rule is intentionally deactivated at first, and the
    " class cx_assert does not exist, but must first be adjusted to your own implementation.

    cx_assert=>assert_bound( lo_instance ).
    cx_assert=>assert_not_bound( is_any_structure-component ).

    cx_assert=>assert_initial( is_parameters-component_name ).
    cx_assert=>assert_not_initial( io_any_instance ).

    cx_assert=>assert_subrc( ). " item must exist
    cx_assert=>assert_subrc( 4 ).
    cx_assert=>assert_subrc( get_expected_subrc_value( param_a = 'abc'
                                                       param_b = 'def' ) ).

    cx_assert=>assert_true( lv_was_initialized ).
    cx_assert=>assert_false( mv_is_valid ).
    cx_assert=>assert_true( line_exists( lts_table[ iv_param_a = 1
                                                    iv_param_b = 'abc' ] ) ).

    cx_assert=>assert_equals( act = ms_data-item_type
                              exp = if_any_interface=>co_any_item_type ).
    cx_assert=>assert_equals( act = lv_timestamp(7)
                              exp = lts_table[ 1 ]-start_timestamp(7) ).

    cx_assert=>assert_differs( act = lo_any_item_instance->ms_data-item_category
                               exp = if_any_interface=>co_any_item_category ).
    cx_assert=>assert_differs( act = sy-subrc
                               exp = 0 ).

    cx_assert=>fail( ).

    cx_assert=>assert_true( xsdbool( <ls_any_field_symbol> IS ASSIGNED ) ).
    cx_assert=>assert_true( xsdbool( <ls_other_field_symbol> IS NOT ASSIGNED ) ).
    cx_assert=>assert_true( xsdbool( is_any_structure IS NOT INITIAL OR is_other_structure IS NOT INITIAL ) ).
    cx_assert=>assert_true( xsdbool( <ls_row>-item_key <= ms_parameters-last_item_key ) ).
    cx_assert=>assert_true( xsdbool( lv_quantity <= 100 ) ).
    cx_assert=>assert_true( xsdbool( abs( <ls_any_field_symbol>-sum_quantity ) > 0 ) ).

    " chains can only be processed if they are first unchained
    cx_assert=>assert_subrc( ).
    cx_assert=>assert_bound( io_instance ).
    cx_assert=>assert_false( iv_is_valid ).
  ENDMETHOD.
ENDCLASS.

" example implementation of the assert class, using simplified CL_ABAP_UNIT_ASSERT method signatures:
CLASS cx_xyz_assert DEFINITION PUBLIC
      INHERITING FROM cx_no_check FINAL
      CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS constructor
      IMPORTING previous LIKE previous OPTIONAL.

    CLASS-METHODS assert_equals
      IMPORTING VALUE(act) TYPE any
                VALUE(exp) TYPE any.

    CLASS-METHODS assert_bound
      IMPORTING VALUE(act) TYPE any.

    " ...
ENDCLASS.

CLASS cx_xyz_assert IMPLEMENTATION.
  METHOD constructor.
    super->constructor( previous = previous ).
  ENDMETHOD.

  METHOD assert_equals.
    IF act <> exp.
      RAISE EXCEPTION NEW cx_xyz_assert( ).
    ENDIF.
  ENDMETHOD.

  METHOD assert_bound.
    IF act IS NOT BOUND.
      RAISE EXCEPTION NEW cx_xyz_assert( ).
    ENDIF.
  ENDMETHOD.

  " ...
ENDCLASS.

Related code