Showing posts with label ABAP Unit. Show all posts
Showing posts with label ABAP Unit. Show all posts

ABAP Unit Testing

Unit Testing is one of the corner stones of TDD ( Test Driven Development ). The main goal of  Unit Testing is to test each individual units of code, and determine if it behaves as you designed it to. Each piece of the "puzzle" is tested independently before being integrated into the larger problem you wish to solve. Finding an error in the complete code is much more complex than isolating each unit and then testing it before integrating it into you code.

The biggest resistance to UNIT Testing is the amount of time required to create the tests when you are dealing with a deadline. But there are there are some evidences that TDD and Unit Testing while having an incease of 10-15% in initial time of development, produce a 40-90% decrease in pre-release defects. This are links from Nachiappan Nagapan (link1link2) and another from Boby George and Laurie Williams (link 3)

Unit Tests provide a safety net of regression tests and validation tests so that you can re-factor and integrate effectively. Creating the unit test before the code helps even further by solidifying the requirements, improving developer focus, and avoid creeping elegance.


SAP provides an embedded framework called ABAP UNIT to aid the Test Driven Development approach.


As a basic example we will use our simple Regular Expression email validation program:

http://abapdevblog.blogspot.pt/2013/08/regular-expressions-in-abap-part-1.html


*&---------------------------------------------------------------------*
*& Report  Z_REGEX_EMAIL
*&
*&---------------------------------------------------------------------*
*&
*&
*&---------------------------------------------------------------------*
REPORT  z_regex_email.

SELECTION-SCREEN BEGIN OF SCREEN 100.
PARAMETERS email TYPE c LENGTH 30 LOWER CASE.
SELECTION-SCREEN END OF SCREEN 100.

CLASS lcl_test_class DEFINITION DEFERRED.

*----------------------------------------------------------------------*
*       CLASS lcl_demo DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_demo DEFINITION
  FRIENDS lcl_test_class. " So that we can test Private & protected methods

  PUBLIC SECTION.
    METHODS: constructor,
            main.
  PRIVATE SECTION.
    METHODS: is_email_valid IMPORTING value(im_email) TYPE char30
                            RETURNING value(re_bool) TYPE abap_bool.

    DATA: mv_email_regex TYPE string.

ENDCLASS.                    "public SECTION.


*----------------------------------------------------------------------*
*       CLASS lcl_demo IMPLEMENTATION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_demo IMPLEMENTATION.
  METHOD constructor.

    " the regular expression to validate the email was retrieved
    " from this wonderful site http://www.regular-expressions.info/
    " which is a great source of knowlege regarding Regular Expression
    mv_email_regex =  '\w+(\.\w+)*@(\w+\.)+((\l|\u){2,4})'.

  ENDMETHOD.                    "class_constructor
  METHOD main.

    CALL SELECTION-SCREEN 100.

    " We use the predifined string function MATCH
    IF abap_true EQ is_email_valid( email ).
      " The Email Matches the Regular expression
      MESSAGE 'Format matches' TYPE 'S'.
    ELSE.
      " There is no match between the email and the regular expression
      MESSAGE 'Format does not Match!' TYPE 'S' DISPLAY LIKE 'E'.
    ENDIF.

  ENDMETHOD.                    "main

  METHOD is_email_valid.
    IF matches( val   = im_email
                regex = mv_email_regex ).
      " The Email Matches the Regular expression
      re_bool = abap_true.
    ELSE.
      re_bool = abap_false.
    ENDIF.
  ENDMETHOD.                    "is_email_valid

ENDCLASS.                    "lcl_demo IMPLEMENTATION



START-OF-SELECTION.
  DATA: lo_demo TYPE REF TO lcl_demo.

  CREATE OBJECT lo_demo.
  lo_demo->main( ).



*----------------------------------------------------------------------*
*       CLASS lcl_Test_Class DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_test_class DEFINITION FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.

  PRIVATE SECTION.
* ================
    DATA:
          f_cut TYPE REF TO lcl_demo.  "class under test


    CLASS-METHODS: class_setup.
    CLASS-METHODS: class_teardown.
    METHODS: setup.
    METHODS: teardown.
    METHODS: email_is_valid FOR TESTING.
    METHODS: email_is_not_valid FOR TESTING.

ENDCLASS.       "lcl_Test_Class
*----------------------------------------------------------------------*
*       CLASS lcl_Test_Class IMPLEMENTATION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_test_class IMPLEMENTATION.
* ====================================

  METHOD class_setup.
* ===================


  ENDMETHOD.       "class_Setup


  METHOD class_teardown.
* ======================


  ENDMETHOD.       "class_Teardown


  METHOD setup.
* =============

    CREATE OBJECT f_cut.
  ENDMETHOD.       "setup


  METHOD teardown.
* ================


  ENDMETHOD.       "teardown

  METHOD email_is_valid.

    cl_abap_unit_assert=>assert_equals(
    EXPORTING
      exp                  = abap_true
      act                  = f_cut->is_email_valid( 'VALID.EMAIL@EMAIL.COM' ) ).

  ENDMETHOD.                    "email_is_valid

  METHOD email_is_not_valid.

    cl_abap_unit_assert=>assert_equals(
    EXPORTING
      exp                  = abap_false
      act                  = f_cut->is_email_valid( 'INVALID@email' ) ).

  ENDMETHOD.                    "email_is_not_valid

ENDCLASS.       "lcl_Test_Class



As you can see we made a couple of changes to the initial program:
  • First the methods from lcl_demo are no longer static methods but instance methods. 
    • This was to show you that the ABAP Unit Framework provides you with some fixture methods to help setup your test data and test object.

  METHOD setup.
* =============
    " There is a SETUP method in a test class, into which you
    " can relocate the data setup that is required before each test.

    CREATE OBJECT f_cut. " we create the instance of the class under test

  ENDMETHOD.       "setup

  • The lcl_demo now has as a friend the new lcl_test_class which has its definition deferred to make the class lcl_test_class known to lcl_demo, regardless of the location of the actual definition of the class in the program. The friendship part is important so that we can grant lcl_test_class access to the protected and private members of lcl_demo class.

CLASS lcl_test_class DEFINITION DEFERRED.

*----------------------------------------------------------------------*
*       CLASS lcl_demo DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_demo DEFINITION
  FRIENDS lcl_test_class. " So that we can test Private & protected method


  • Then we have our new lcl_test_class with a special definition

CLASS lcl_test_class DEFINITION FOR TESTING
                     DURATION SHORT
                     RISK LEVEL HARMLESS.


    • The FOR TESTING addition is used to defined a class as a test class for the ABAP Unit tool.
    • These additions assign test properties to a test class. RISK LEVEL defines the risk level for a test and DURATION the expected execution time. The test properties are checked during when the test is executed. Tests whose risk level is higher than allowed in a system are not executed. Tests that run longer than the expected execution time are terminated.  


    • And our test methods.

    METHODS: email_is_valid FOR TESTING.
    METHODS: email_is_not_valid FOR TESTING.

  METHOD email_is_valid.

    cl_abap_unit_assert=>assert_equals(
    EXPORTING
      exp                  = abap_true
      act                  = f_cut->is_email_valid( 'VALID.EMAIL@EMAIL.COM' ) ).

  ENDMETHOD.                    "email_is_valid

  METHOD email_is_not_valid.

    cl_abap_unit_assert=>assert_equals(
    EXPORTING
      exp                  = abap_false
      act                  = f_cut->is_email_valid( 'INVALID@email' ) ).

  ENDMETHOD.                    "email_is_not_valid


In out initial coding we had a program while re-factoring the is_email_vaild method and introduced a bug. we forgot to change out match variable from email to the importing parameter im_email.


  METHOD is_email_valid.
    IF matches( val   = email
                regex = mv_email_regex ).
      " The Email Matches the Regular expression
      re_bool = abap_true.
    ELSE.
      re_bool = abap_false.
    ENDIF.
  ENDMETHOD.                    "is_email_valid



So when we tested our code  (Ctrl+Shift+F10) we got the following report showing that we have an error.


Execute the Unit Tests



ABAP Unit Report

Being an easy fix we change the code so that we use the import parameter im_email.


  METHOD is_email_valid.
    IF matches( val   = im_email
                regex = mv_email_regex ).
      " The Email Matches the Regular expression
      re_bool = abap_true.
    ELSE.
      re_bool = abap_false.
    ENDIF.
  ENDMETHOD.                    "is_email_valid

Testing again produces the message saying that our method passed all the tests.


Our Unit Tests Passed


Unit Testing - Part 1

Functionality

The class CL_AUNIT_ASSERT contains the following static methods for the verification of test expectations withinABAP Unit test methods:
  • ASSERT_EQUALS: Ensures the equality of two data objects
  • ASSERT_EQUALS_F: Ensures the equality of two floats with a rel. tolerance.
  • ASSERT_DIFFERS:Ensures the inequality of two elementary data objects.
  • ASSERT_BOUND: Ensures whether the reference of a reference variable is valid
  • ASSERT_NOT_BOUND: Ensures whether the reference of a reference variable is invalid
  • ASSERT_INITIAL: nsures that a data object has its initial value
  • ASSERT_NOT_INITIAL: Ensures that a data object does not have its initial value
  • ASSERT_SUBRC: Requests specific value of sy-subrc
  • FAIL: Terminates the test with an error
  • ABORT: Cancels the test due to a missing context (ideally in setup method)

All the methods have the optional importing parameters MSG, LEVEL, and QUIT, which always have the same meaning:
  • MSG (Type: CSEQUENCE): Contains a more detailed description of the error (if applicable)
  • LEVEL (Type: AUNIT_LEVEL): ndicates the severity of the error; the following values are possible:
  • TOLERABLE - Minor error (may be tolerable)
  • CRITICAL - Critical error (default value)
  • FATAL - Fatal error

  • QUIT (Type: AUNIT_FLOWCTRL):Determines the flow control in the case of an error. The following values can be passed:
NO - No termination in the case of an error; processing of the current method continues after the relevant method is called
METHOD - The currently processed test method is terminated (default value)
CLASS - The currently processed test class is terminated
PROGRAM - The test of the currently processed main program is terminated: The current test class is terminated and all other test classes in the program are ignored.


All the assert methods have the following mandatory importing parameter:
ACT (Type: Any)
The object to be verified
The comparing method ASSERT_EQUALS additionally requires a parameter for the expectation:
EXP (Type: Any)
The identically expected object
TOL (Type: f)
Allows you to compare floating point numbers for the passed tolerance