Implementing Code Tests with ABAP Unit

Objectives

After completing this lesson, you will be able to:

  • Implement a test class
  • Run an ABAP unit test

ABAP Unit

Module Tests

Whenever programmers write or change code, there is a fair chance that they introduce programming errors. Therefore, testing is a crucial part of every development project.

In modern programming, the code is structured in reusable classes and methods. To detect and localize potential programming errors, a thorough test of each modularization unit is needed.

Let's look at an example.

ABAP Unit - Implement Module Tests in ABAP

ABAP Unit is a technique that allows you to implement module tests with the ABAP language. You can trigger test execution manually during development but also automatically on a larger scale and a regular basis.

ABAP Unit tests are implemented as methods of specially designated ABAP classes. These test methods serves as test scripts, with which code under test can be run, and with which the results can be evaluated.

Let's continue with our example.

It is very important that once the tests are there, you can perform them any time and as often as you want: Before initial shipment, after having applied changes to the productive code, or for error analysis when a user reports a problem. And, executing the tests costs you only a few mouse clicks.

Important Features of ABAP Unit

Unit Test Classes

Addition, FOR TESTING, in the class definition distinguishes a test class from an ordinary ABAP class. The addition is available for local classes as well as for global classes.

Addition, RISK LEVEL, is used to assign a risk level to the test. If the addition is missing, risk level CRITICAL is used by default.

Central settings on client level can disallow the execution of tests with certain risk level.

The following risk level values are available:

CRITICAL

A test changes system settings or customizing data

DANGEROUS

A test changes persistent data

HARMLESS

A test does not change system settings or persistent data

Addition DURATION specifies the expected execution time. Central settings on client level can be used to define upper runtime limits for the three values.

The following duration values are available:

SHORT

An imperceptible execution time of some few seconds is expected.

MEDIUM

A noticeable execution time of around a minute is expected.

LONG

A very noticeable execution time of more than one minute is expected.

A test class can have two kinds of methods:

Test Methods

Test methods are defined with addition FOR TESTING after the method name. Each test method represents one test. The ABAP Unit framework performs this test by calling the related test method. Test methods must not have any parameters.

Helper Methods

Helper methods are ordinary methods of the test class. They are not called by the ABAP Unit framework. You can use helper methods to structure the code of your test methods or if you want to reuse the same functionality in several test methods. Helper methods can have any number of parameters.

Note

The ABAP Unit framework is able to call all test methods, even if their visibility is set to PRIVATE. Actually, it is recommended to define test methods in the private section of the test class to make sure the test classes are not called directly anywhere in the code.

A test class should contain at least one test method. A test method must not have any parameters.

When executing the tests of a test class, the ABAP Unit framework calls all tests in the test class in an undetermined order.

If you are going to define local test classes inside a global ABAP class, there is a dedicated place to do so. While ordinary local classes are defined on the Local Types tab, local test classes should be defined on the Test Classes tab.

As illustrated in the figure, to generate the definition part and implementation part of a local test class, ADT offers the code template testClass.

In the demonstration, How to Define and Implement a Test Class, you will see how to invoke this code template.

Unit Test Implementation

Service Class CL_ABAP_UNIT_ASSERT

In general, the implementation of a test method has the following structure:

  1. Execute the productive coding under test
  2. Analyze the outcome
  3. Report unexpected outcome to the ABAP Unit framework

For step 3, the ABAP Unit framework provides global service class CL_ABAP_UNIT_ASSERT. Test methods call the static methods of this class to report errors and to influence the test execution (for example, skip one or several tests because prerequisites are not met).

  • Method fail( ) reports an unconditional error. Usually, a call of this method is surrounded by a control structure like IF … ENDIF. or TRY … ENDTRY to ensure it only is reached under a condition.
  • Methods starting with assert_ check on a certain expectation and report an error if this expectation is not met.
    • Method assert_equals( ), for example, compares the content of two data objects and reports an error if they differ.
    • Method assert_differs( ) does the same but reports an error if the data objects have the same content.

Parameters of Method fail( )

Watch the following video to learn more.

Parameters of Assert-Methods

All assert-methods have optional importing parameters MSG, LEVEL, and QUIT, which always have the same meaning as in method fail( ).

In addition, most assert-methods have an importing parameter ACT for the data object to be verified.

Comparing methods, like assert_equals( ), assert_differs( ), and so on, additionally have a parameter EXP for the expected value.

How to Define and Implement a Test Class

In this demonstration, you will see how to create and implement a local test class.

Unit Test Execution

There are two ways to execute ABAP Unit Tests: Interactive tests during development and Mass tests with ABAP Test Cockpit.

Watch the following video to know more about the Unit Test Execution.

ABAP Unit Test Results

In ADT, the latest ABAP Unit test result is displayed in the ABAP Unit View.

The summary on top displays the total number of test methods that were executed and the overall duration of the test in milliseconds.

Use the check boxes in the summary to filter the result display: Only tests that failed, only tests with warnings, only tests the ended successfully, and so on.

The result itself is displayed as a tree with the repository objects on top, for example, the global class. The nodes on second level represent the local test classes, and the end points correspond to test methods. The icons help you to distinguish between successful tests, failures, and so on.

A left-click selects the test method and displays details on the right hand-side. In the example, you can see the severity (Critical Assertion Error for severity medium), the value of parameter MSG ('No Exception'), and the value of parameter DETAILS (the text expanded below Details).

How to Run a Unit Test and Analyze the Result

In this demonstration, you will see how to execute the unit tests and analyze the results.

Test Fixtures and Prerequisites

Methods for Test Fixtures

Sometimes a test needs a certain configuration before it can run properly. Such a test configuration is referred as test fixture. A test fixture may consist of test data, test objects, and resources.

To create and remove fixtures, you can implement additional methods in a test class. These methods have predefined names, and they are automatically called by the ABAP runtime environment when the test is executed.

The following fixture methods exist:

  • SETUP

    This instance method is called before each test of the test class. Use this method for fixtures that you want to create again for each test.

  • TEARDOWN

    This instance method is called after each test of the test class. Use this method to undo the changes you made in method SETUP. The use of TEARDOWN is particularly important if SETUP makes changes to persistent data (system configuration, customizing, master data, and so on).

  • CLASS_SETUP

    This static method is executed once before the first test of the test class. Only use this method for fixtures that are time consuming and for which you are sure that the settings are not changed by any of the test methods.

  • CLASS_TEARDOWN

    This static method is executed once after the last test of the test class. Use this method to undo changes you made in method CLASS_SETUP.

Flow Logic of ABAP Unit Test

This graphic illustrates the program flow of ABAP UNIT for a single test class.

  • At first, the test class is loaded into the program memory. If the test class contains a static constructor (static method CLASS_CONSTRUCTOR) this method is executed as usual.
  • Then, the tests of this test class are performed. Each test starts with the creation of an instance of the test class. If the test class contains an instance constructor (Method CONSTRUCTOR) it is executed as usual.
  • Then, the ABAP Unit framework calls the test method.
  • After the test, the instance is discarded.

If there is more than one test method in the test class, a new instance is created for each test.

After the last test method, the processing of this test class is finished and the framework continues with the next test class, if any.

Phase Model of ABAP Unit Test

This graphic illustrates the Phase Model for ABAP Unit Tests, that is, the points in time at which the framework calls fixture methods.

CLASS_SETUP is called only once, after the static constructor and before the first test.

CLASS_TEARDOWN is called before the framework starts processing of the next test class.

SETUP is called after the instance constructor and before the test execution.

TEARDOWN is called before the framework discards the test class instance.

Reporting Missing Prerequisites

You already learned how to use methods of service class CL_ABAP_UNIT_ASSERT to report failed tests.

But what are you supposed to do in case of a missing prerequisite, for example, missing authorizations? Or a situation where a SETUP method has problems preparing the test?

Exactly for those situations, class CL_ABAP_UNIT_ASSERT contains a method skip( ) and several methods starting with ASSUME_.

The methods work quite similar to fail( ) and the assert-methods, but they look different in the UNIT test result. This makes it easier to distinguish between errors in the tested coding and errors in the system configuration or the test design.

Let's have a look at an example:

Method test_with_fail( ) calls method cl_abap_unit_assert=>fail( ). This method is counted as a failed test and the Failure Trace for this method displays Critical Assertion Error in front of the message text.

Method test_with_skip( ) calls method cl_abap_unit_assert=>skip( ). This method is counted as an aborted test and the Failure Trace for this method displays Missing Prerequisites in front of the message text.

Note

There are no parameters QUIT and LEVEL in methods skip( ) and the assume-methods. With missing prerequisites there is no point in continuing the test, and we do not distinguish between different severities.

How to Run a Complex Unit Test with SETUP

In this demonstration, you will see how to run a more complex test that contains a setup method.

Implement and Run an ABAP Unit Test

You notice that something is not right with the output of your code. In particular, the date for the next available cargo flight seems to be too far in the future. You define and implement an ABAP Unit test for method find_cargo_flight of local class lcl_carrier to further analyze this issue.

Template:

  • /LRN/CL_S4D401_ATS_CHECKED (Global Class)

Solution:

  • /LRN/CL_S4D401_ATS_UNIT_TEST (Global Class)

Task 1: Copy Template (Optional)

Copy the template class. If you finished the previous exercise, you can skip this task and continue editing your class ZCL_##_SOLUTION.

Steps

  1. Copy class /LRN/CL_S4D401_ATS_CHECKED to a class in your own package (suggested name: ZCL_##_SOLUTION, where ## stands for your group number).

    1. In the Project Explorer view, right-click class /LRN/CL_S4D401_ATS_UNIT_CHECK to open the context menu.

    2. From the context menu, choose Duplicate ....

    3. Enter the name of your package in the Package field. In the Name field, enter the name ZCL_##_SOLUTION, where ## stands for your group number.

    4. Adjust the description and choose Next.

    5. Confirm the transport request and choose Finish.

  2. Activate the copy.

    1. Press Ctrl + F3 to activate the class.

Task 2: Define Local Test Class

In your global class, define a local test class (suggested name: ltcl_find_flights). Define and implement a test for method find_cargo_flight of class lcl_carrier.

Steps

  1. Create a local test class ltcl_test .

    1. Navigate to the Test Classes tab.

    2. Place the cursor at the end of the comment in the first source code line and press Enter.

    3. In the new source code line, type test and press Ctrl + Space to invoke code completion.

    4. Choose source code template testClass - Test class (ABAP Unit) and press Enter.

    5. While the preliminary class name ltcl_ is highlighted, type in the full class name ltcl_find_flights.

  2. Use a quick fix to rename the predefined test method (suggested name: test_find_cargo_flight).

    1. Right-click on first_test, and choose Quick Fix. From the list that appears, choose rename first_test. While the name is highlighted, type in the new name test_find_cargo_flight.

  3. Implement method test_find_cargo_flight. Read an arbitrary single cargo flight from the database table /LRN/CARGOFLIGHT. Read the key fields (carrier_id, connection_id, flight_date) and the two airports (airport_from_id and airport_to_id). Store the result in a suitable data object (suggested name: some_flight_data).

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567891011
      METHOD test_find_cargo_flight. SELECT SINGLE FROM /lrn/cargoflight FIELDS carrier_id, connection_id, flight_date, airport_from_id, airport_to_id INTO @DATA(some_flight_data). ENDMETHOD.
  4. If there are no data in table /LRN/CARGOFLIGHT, report the test as failed.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      123456789101112131415
      METHOD test_find_cargo_flight. SELECT SINGLE FROM /lrn/cargoflight FIELDS carrier_id, connection_id, flight_date, airport_from_id, airport_to_id INTO @DATA(some_flight_data). IF sy-subrc <> 0. cl_abap_unit_assert=>fail( `No data in table /LRN/CARGOFLIGHT` ). ENDIF. ENDMETHOD.
  5. If you were able to read an arbitrary record from table /LRN/CARGOFLIGHT, use the carrier_id value of this record to create an instance of class lcl_carrier (suggested name for the reference: carrier).

    1. Add the following code at the end of the method:

      Code Snippet
      Copy code
      Switch to dark mode
      123
      data(the_carrier) = new lcl_carrier( i_carrier_id = some_flight_data-carrier_id ).
  6. If the constructor of class lcl_carrier raises an exception report the test as failed.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567
      data(the_carrier) = NEW lcl_carrier( i_carrier_id = some_flight_data-carrier_id ). CATCH cx_abap_invalid_value. cl_abap_unit_assert=>fail( `Unable to instantiate lcl_carrier` ). ENDTRY.
  7. If the instantiation was successful, call method find_cargo_flight for the instance of lcl_carrier. Use the airports and the flight date of your cargo flight as input. Set the minimum free cargo to 1. Store the result in suitable data objects (suggested names: flight and days_later).

    1. Add the following code at the end of the method:

      Code Snippet
      Copy code
      Switch to dark mode
      123456789101112
      the_carrier->find_cargo_flight( EXPORTING i_airport_from_id = some_flight_data-airport_from_id i_airport_to_id = some_flight_data-airport_to_id i_from_date = some_flight_data-flight_date i_cargo = 1 IMPORTING e_flight = data(flight) e_days_later = data(days_later) ).
  8. Analyze the output of the method. Call suitable assert-methods to ensure that parameter e_flight returns a valid object reference and that parameter e_days_later returns zero.

    Note

    (A value of days_later = 0 means that the method found a suitable flight directly on the requested day. This is what we expect if we use the properties of an existing flight as input.
    1. At the end of the method, add the following code:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567891011
      cl_abap_unit_assert=>assert_bound( act = flight msg = `Method find_cargo_flight does not return a result` ). cl_abap_unit_assert=>assert_equals( act = days_later exp = 0 msg = `Method find_cargo_flight returns wrong result` ).
  9. Activate your class.

    1. Press Ctrl + F3 to activate the class.

Task 3: Execute Unit Test

Execute the unit test. Analyze the result and adjust the implementation of method find_cargo_flight if the test returns an error.

Steps

  1. Execute the unit tests in your global class ZCL_00_SOLUTION.

    1. In the project explorer, right-click on the class ZCL_00_SOLUTION and choose Run as... → ABAP Unit Test

  2. When the test finished, analyze the outcome on the ABAP Unit view.

    1. If it is not already open, click the ABAP Unit tab in the bottom section of the screen.

    2. You should see a value 1 next to the Failures / Errors icon.

    3. In the tree, choose the test method name (ZCL_00_SOLUTION → ltcl_test_find_flights → test_find_cargo_flight) to display the details of the error.

  3. Analyze the code of method find_cargo_flight and fix the error.

    1. In the tree, double-click test_find_cargo_flight to navigate to the implementation of the test method.

    2. Scroll down to the call of method find_cargo_flight. Place the cursor on the method name and press F3 to navigate to its implementation.

    3. Find the calculation of days_later. Here, the date of the flight is subtracted from the actual date. This should be the other way round.

    4. To fix the error, add a comment sign in front of this statement:

      Code Snippet
      Copy code
      Switch to dark mode
      123
      * DATA(days_later) = i_from_date - flight->flight_date.

      And replace it with this code:

      Code Snippet
      Copy code
      Switch to dark mode
      123
      DATA(days_later) = flight->flight_date - i_from_date .
  4. Activate your class and rerun the unit test to confirm that the error is now gone.

    1. Press Ctrl + F3 to activate the class.

    2. Choose Rerun Tests from the toolbar on the ABAP Unit tab.

    3. Make sure the test now shows zero errors/failures.

  5. Execute the class as console app to ensure that the date of the next available cargo flight is reasonably near in the future.

Task 4: Implement CLASS_SETUP (Optional)

You plan to add more test methods to your test class which will all need an instance of lcl_carrier. To improve the performance of your tests, you move the time consuming instantiation of class lcl_carrier to the class_setup method because this method is executed only once for all tests of the test class.

Steps

  1. Define a static method class_setup (without parameters) and use a quick fix to add the method implementation.

    1. Add the following code to the private section of class ltc_find_flights.

      Code Snippet
      Copy code
      Switch to dark mode
      123
      CLASS-METHODS class_setup.
    2. Right-click on test_find_cargo_flight and choose Quick Fix.

    3. From the suggestion list, choose Add implementation for test_find_cargo_flight.

  2. In the test class, declare a private static attribute in which you can store a reference to an instance of class lcl_carrier (suggested name: the_carrier).

    1. Add the following code to the private section of class ltc_find_flights.

      Code Snippet
      Copy code
      Switch to dark mode
      123
      CLASS-DATA the_carrier TYPE REF TO lcl_carrier.
  3. Declare a private static attribute in which you can store one record from database table /LRN/CARGOFLIGHT (suggested name: some_flight_data).

    1. Add the following code to the private section of class ltc_find_flights.

      Code Snippet
      Copy code
      Switch to dark mode
      123
      CLASS-DATA some_flight_data TYPE /lrn/cargoflight.
  4. Move the SELECT statement and the instantiation of class lcl_carrier from method test_find_cargo_flight to method class_setup. Store the data from the database in static attribute some_flight_data and the reference to the instance of lcl_carrier in static attribute the_carrier.

    1. Cut the following code from the implementation of method test_find_cargo_flight and paste it into the implementation of method class_setup.

      Code Snippet
      Copy code
      Switch to dark mode
      123456789101112131415161718
      SELECT SINGLE FROM /lrn/cargoflight FIELDS carrier_id, connection_id, flight_date, airport_from_id, airport_to_id INTO @DATA(some_flight_data). IF sy-subrc <> 0. cl_abap_unit_assert=>fail( `No data in table /lrn/CARGOFLIGHT` ). ENDIF. TRY. DATA(the_carrier) = NEW lcl_carrier( i_carrier_id = some_flight_data-carrier_id ). CATCH cx_abap_invalid_value. cl_abap_unit_assert=>fail( `Unable to instantiate lcl_carrier` ). ENDTRY.
    2. Replace INTO @DATA(some_flight_sata) with INTO CORRESPONDING FIELDS OF @some_flight_data.

    3. Replace DATA(the_carrier) with the_carrier.

    4. Now the code of method class_setup should look like this:

      Code Snippet
      Copy code
      Switch to dark mode
      12345678910111213141516171819
      METHOD class_setup. SELECT SINGLE FROM /lrn/cargoflight FIELDS carrier_id, connection_id, flight_date, airport_from_id, airport_to_id INTO CORRESPONDING FIELDS OF @some_flight_data. IF sy-subrc <> 0. cl_abap_unit_assert=>fail( `No data in table /lrn/CARGOFLIGHT` ). ENDIF. TRY. the_carrier = NEW lcl_carrier( i_carrier_id = some_flight_data-carrier_id ). CATCH cx_abap_invalid_value. cl_abap_unit_assert=>fail( `Unable to instantiate lcl_carrier` ). ENDTRY. ENDMETHOD.
  5. Activate the class and run the unit test again to make sure it still works.

    1. Press Ctrl + F3 to activate the class.

    2. Run the unit tests as before.

Log in to track your progress & complete quizzes