Test driven development in ABAP – Unit test classes

Test Driven Development using ABAP UnitThis article provides info about creation an ABAP unit test class that might significantly reduce the future maintenance cost at the testing level. The flow is simple – you design your functional tests in advance and then you create your code. You can run the test class repeatedly to tell you which tests still didn’t pass.

The main idea of test driven development

  • Create methods inside your class specifying just their interface, but no code inside (1. Red)
  • Write tests for your class, ideally 1 test method/1 class method (1. Red)
  • Make all the test pass = implement the real code (2. Green)
  • Refactoring (3. Refactor)

It is always good to modularize our programs to achieve easier maintenance and re-usability in the future. If we want to check specific parts of our programs for correctness repeatedly, it’s the right decision to use a tool for unit testing – ABAP Unit.

ABAP unit is based on ABAP objects where the global class CL_AUNIT_ASSERT contains methods which can be used for testing .Tests are usually implemented in local classes. Inside the local class the necessary method from the global class can be called for testing. These test classes can be written inside the program or class of function module for which the test is to be done.

Even if it never affect your production code, you should be very careful your tests don’t modify any production data.

Few differences between Normal and Test class in ABAP

  • Each test class must have FOR TESTING addition
  • Each method of the test class that is to be executed as unit test method must have FOR TESTING addition (the test class can of course have normal/supporting methods)
  • You should specify the risk level and duration
  • To be able to call test methods directly, it’s good idea to inherit from class CL_AUNIT_ASSERT
" Definition as of release 700
CLASS lcl_test DEFINITION 
  "#AU Risk_Level Harmless
  "#AU Duration Short
  FOR TESTING
  INHERITING FROM cl_aunit_assert.

  PRIVATE SECTION.
    METHODS:
      TEST_METHOD_A 
        FOR TESTING.
ENDCLASS.
" Definition as of release 702
CLASS lcl_test DEFINITION FOR TESTING
  RISK LEVEL HARMLESS
  DURATION SHORT  
  INHERITING FROM cl_aunit_assert.

  PRIVATE SECTION.
    METHODS:
      TEST_METHOD_A 
        FOR TESTING.
ENDCLASS.

Example program with local classes

We will follow the test driven development approach: create dummy methods -> write tests for them -> implement the methods until all test are passed.

Create the class with dummy methods

*---------------------------------------------------------------*
*       CLASS LCL_MATH DEFINITION
*---------------------------------------------------------------*
CLASS lcl_math DEFINITION.
  PUBLIC SECTION.
    METHODS:
      add
        IMPORTING
          i_val1 TYPE i
          i_val2 TYPE i
        RETURNING value(result) TYPE i,
      divide
        IMPORTING
          i_val1 TYPE i
          i_val2 TYPE i
        RETURNING value(result) TYPE f
        RAISING cx_sy_arithmetic_error,
      factorial
        IMPORTING
          n TYPE i
        RETURNING value(result) TYPE i.
ENDCLASS.                    "lcl_test  DEFINITION
*--------------------------------------------------------------*
* CLASS lcl_math IMPLEMENTATION
*--------------------------------------------------------------*
CLASS lcl_math IMPLEMENTATION.
  METHOD add.
  ENDMETHOD. "add

  METHOD divide.
  ENDMETHOD. "divide

  METHOD factorial.
  ENDMETHOD. "factorial
ENDCLASS. "lcl_math IMPLEMENTATION

Create the test class

*--------------------------------------------------------------*
*       CLASS lcl_test  DEFINITION
*--------------------------------------------------------------*
CLASS lcl_test DEFINITION
  "#AU Risk_Level Harmless
  "#AU Duration Short
  FOR TESTING
  INHERITING FROM cl_aunit_assert.

  PRIVATE SECTION.
    METHODS:
      test_add FOR TESTING,
      test_divide FOR TESTING,
      test_factorial FOR TESTING.
ENDCLASS.                    "lcl_test  DEFINITION
*--------------------------------------------------------------*
*       CLASS lcl_test IMPLEMENTATION
*--------------------------------------------------------------*
CLASS lcl_test IMPLEMENTATION.
  METHOD test_add.
    DATA:
      lr_class TYPE REF TO lcl_math,
      result TYPE i.

    CREATE OBJECT lr_class.
    result = lr_class->add( i_val1 = 5
                            i_val2 = 7 ).

    assert_equals( act   = result
                   exp   = '12'
                   msg   = 'Addition not computed correctly'
                   level = critical
                   quit  = no
    ).
  ENDMETHOD.                    "test_add
  METHOD test_divide.
    DATA:
      lr_class TYPE REF TO lcl_math,
      l_ex TYPE REF TO cx_sy_arithmetic_error,
      result TYPE i.

    CREATE OBJECT lr_class.

    TRY.
        result = lr_class->divide( i_val1 = 32
                                   i_val2 = 6 ).
      CATCH cx_sy_arithmetic_error INTO l_ex.
    ENDTRY.

    assert_not_initial( act   = l_ex
                        msg   = 'Exception was not expected'
                        level = tolerable
                        quit  = no
    ).

    assert_equals( act   = result
                   exp   = '5'
                   msg   = 'Division not computed correctly'
                   level = critical
                   quit  = no
                   tol   = '0.999'
    ).

    TRY.
        result = lr_class->divide( i_val1 = 32
                                   i_val2 = 0 ).
      CATCH cx_sy_arithmetic_error INTO l_ex.
    ENDTRY.

    assert_not_initial( act   = l_ex
                        msg   = 'Exception was expected'
                        level = tolerable
                        quit  = no
     ).
  ENDMETHOD.                    "test_divide
  METHOD test_factorial.
    DATA:
      lr_class TYPE REF TO lcl_math,
      result TYPE i.

    CREATE OBJECT lr_class.
    result = lr_class->factorial( 4 ).

    assert_equals( act    = result
                   exp    = '24'
                   msg    = 'Factorial not computed correctly'
                   level  = critical
                   quit   = method
     ).
  ENDMETHOD.                    "test_factorial
ENDCLASS.                    "lcl_test IMPLEMENTATION

Output 1

ABAP Unit test - failures

Implementing methods

*--------------------------------------------------------------*
* CLASS lcl_math IMPLEMENTATION
*--------------------------------------------------------------*
CLASS lcl_math IMPLEMENTATION.
  METHOD add.
    result = i_val1 + i_val2.
  ENDMETHOD. "add

  METHOD divide.
    result = i_val1 / i_val2.
  ENDMETHOD. "divide

  METHOD factorial.
    result = 1.
    IF n = 0.
      RETURN.
    ELSE.
      DO n TIMES.
        result = result * sy-index.
      ENDDO.
    ENDIF.
  ENDMETHOD. "factorial
ENDCLASS. "lcl_math IMPLEMENTATION

Output 2

ABAP Unit test - successfully tested program

Client specific settings for unit tests:

You can maintain the client based configuration for switch off Unit Test, allowed preference for Risk level and Duration in the transaction SAUNIT_CLIENT_SETUP. You must not change the settings on the fly as all of your tests may become non-executable because they are having different Risk Level and Duration than defined in the settings. Setting screen looks similar to this:

Unit test client settings

Execution risk levels:

  • HARMLESS – The execution of this test doesn’t affect any existing processes or Database.
  • DANGEROUS – This type of test makes changes to DB or persistent Data
  • CRITICAL – This type of test would make changes to Customization as well as the Persistent data. A careful look is required.

Duration options:

  • SHORT – Gets executed very fast. This is default setting at the client settings. Generally < 1 minute
  • MEDIUM  – Gets executed in a bit. Little bit extra time than the short duration. In the range of 1 minute to 10 minutes
  • LONG – takes a while to process the test. The execution time would be more than 10 minutes

Special methods in test classes (FIXTURES)

Test fixture is the test configuration like test data or test objects. This data would be used within the test methods. Fixture would be executed before the actual test method gets executed. So when the test is getting performed, the test data or test objects setup in fixture method can be used.

In ABAP, the test fixture is achieved using these predefined methods. These method would be called automatically by ABAP framework if they exist in the test class.

  • SETUP – Instance method SETUP would be called before each test within the test class
  • TEARDOWN – In contrary to SETUP, instance method TEARDOWN would be called after each testwithin the test class
  • CLASS_SETUP – Similar to SETUP, static method CLASS_SETUP would be used to set up the data once before the first test in the class gets executed
  • CLASS_TEARDOWN – Like TEARDOWN, static method TEARDOWN would be used to clear the data once after the last test in the class gets executed

Method SETUP( )

Special method SETUP( ) is used to setup the common test data for the same test methods of the same test class or of the inherited test class. This method would be called before calling the test method by the ABAP Unit framework. So, basically it would be called as many times as many test methods are there in a single test class.

In this method, you should generally setup your test data which can be leveraged by various different test within the same test class. Simple example, would be to setup default value for a variable, or instantiate the object for Production code.

Method TEARDOWN( )

Special method TEARDOWN( ) should be used to clear down the test data which was used by the actual test. You should use this method to clear the test data and make sure they are ready to use by the next Test method. This method would be called after calling the test method by the ABAP Unit framework. Similar to SETUP, this method would be called as many times as many test methods are there in a single test class.

In this method, you should generally setup your test data which can be leveraged by various different test within the same test class. Simple example, would be to clear the product code objects or all attributes of the test class and make sure it is ready for next execution.

Method CLASS_SETUP( ) & CLASS_TEARDOWN( )

Method CLASS_SETUP( ) is a static method. Set up the test data and save it into some temporary variable. We can use these test data into the SETUP method. The purpose of CLASS_SETUP is to set up the same data like a configuration which would be used by all test methods but NONE of the test method would be modifying it.

Static method CLASS_TEARDOWN would to make sure you clear up all the data and related attributes used in the test class before leaving the class.

Methods available for testing in class CL_AUNIT_ASSERT :

  • ABORT – Test terminated due to missing context
  • ASSERT_BOUND – Ensure the validity of the reference of a reference variable
  • ASSERT_CHAR_CP – Ensure that character string fits template

  • ASSERT_CHAR_NP – Ensure that character string does not fit template
  • ASSERT_DIFFERS – Esnure difference between two (elementary) data objects

  • ASSERT_EQUALS – Ensure equality of two data objects
  • ASSERT_EQUALS_F – Save Approximate Consistency of Two Floating Point Numbers

  • ASSERT_INITIAL – Ensure that object has its initial value
  • ASSERT_NOT_BOUND – Ensure invalidity of the reference of a reference variable
  • ASSERT_NOT_INITIAL – Ensure that object does NOT have its initial value

  • ASSERT_SUBRC – Request specific value of return code subrc
  • ASSERT_THAT – Test compliance with a condition

  • FAIL – Termination of Test with Error

Parameters used by the test methods above

  • ACT – Actual result you got using your program
  • EXP – Expected result
  • MSG – Message to be displayed in case of failure
  • LEVEL – Level of criticality
  • QUIT – Quit options
  • TOL – Tolerance level for results of type F

Levels of criticality

  • 0 (TOLERABLE)
  • 1 (CRITICAL)
  • 2 (FATAL)

Quit options

  • 0 (NO) – It will continue processing the current test method
  • 1 (METHOD) – Processing of the current test method is interrupted
  • 2 (CLASS) – Processing of the current method and all remaining test methods of current test class is interrupted
  • 3 (PROGRAM) – Cancel execution of all remaining test classes for the tested program

Calling ABAP Unit tests in Code Inspector

If you call your Code Inspector, you can include a call to your unit tests. Just run your code inspector, in Top menu go to Utilities -> DEFAULT Check Variant -> Maintain and select the check box at ABAP Unit
ABAP Unit test - Code inspector settings

After you run Code Inspector, ABAP Unit runs all your Test classes and their results will be included in the final CI report
ABAP Unit test - Code Inspector output

Leave a Reply