Unit Tests in ABAP using a mocking framework

Introduction

Unit Tests are an integral part of the software development lifecycle. The purpose of tests is to ensure the proper functionality of a system. Also, if changes are made to specific parts of the system, Unit Tests can immediately show if any functionality has been broken during the last update.

A common problem in unit testing is to separate the system under test, which is subject to the unit test, from other parts which are not to be tested. Common parts, which need to be separated and replaced specifically for unit tests are usually business object repositories or database access components as well as components which rely on third party systems.

This blog is not about unit testing in ABAP. This blog is about developing them faster.

Scenario

For demo purposes, I build up a scenario. It includes a simple flight observer, which has access to a flight information system.

Furthermore, it has access to an alert component which raises mail, SMS etc. alerts.

Both components are dependencies on which the observer relies on. Both components will need to be replaced by mockups for testing purpose since they are not subject to the unit test. Rather, the flight observer is to be tested, in the following sections referred to as the system under test (SUT).

The SUT is implemented by /LEOS/CL_FLIGHT_OBSERVER.

The flight information component is described by the interface /LEOS/IF_IS_IN_TIME_INFO.

The alert processor is described by the interface /LEOS/IF_FLIGHT_ALERT_PROCESS.

UML

The validation logic is very simple: It decides, weather an alert for a specific flight is raised or not. Every delay with more than 60 minutes is getting raised. However, in any other real scenario, there wouldn’t be such a simple hard coded routine but for demo purposes it is fine.
All dependencies to the two other components are set via its constructor using dependency injection.

Unit Test realization Part I

The method which is going to be tested is described below and implements the 60-minutes rule: every delay of more than 60 minutes is getting delegated to the alert system.

This implementation is not a challenge at all. More tedious is the implementation of local classes which are implementing the functionality of the two components on which the SUT depends. These implementations are called mocks.

The flight information access, implementing /LEOS/IF_IS_IN_TIME_INFO, would have to return flights which are sometimes in time and sometimes not. This flight information would have to be hard coded in the unit test. It might look like this:

There would be another mockup for the alert component, tracking the number of calls made to the method ALERT_DELAY( ). We do not show it here.

The setup routine for the unit test to setup all the required objects would look like this:

The test now calls the SUT with various flight data. Some data will lead to a delay, like flight LH / 402 / in November 9th 2012, some will not (like LH / 402 in November 10th 2012). The test is going to evaluate if the alert processor has been called or not, depending on the requested flight data.

Unit Test realization Part II

The described approach requires  a lot of coding to implement the mockups. This coding is never ever reused and basically a waste of time.

This is where mocking frameworks come into place.

Mocking Frameworks are not meant for productive usage, however, their only usage is to save coding in unit tests, hence, development efforts.

The same mockup, which returns flight information data could be mocked using a mocking framework:

The whole mockup implementation is described by only two lines of ABAP coding. Three more lines include data declarations and one line actually generates the mock object for you. This sums up to 8 lines of code, in comparison to 14 lines of code which are needed for the manual implementation.

The mockup for the alert processor is even more simple, since its only purpose is to track the number of calls made against method ALERT_DELAY. This feature is already included in the mocking framework as described in the test coding utilizing these mocks. You would just need to call method HAS_METHOD_BEEN_CALLED( … ) on the corresponding mocker object.

Unit Test Coding

REPORT /leos/_test_flight_observer.

*----------------------------------------------------------------------*
* CLASS lcl_test_observer DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_test_observer DEFINITION FOR TESTING.
"#AU Risk_Level Harmless
"#AU Duration Short
PROTECTED SECTION.

DATA mo_is_in_time_access TYPE REF TO /leos/if_is_in_time_info .
DATA mo_is_in_time_mocker TYPE REF TO /leos/if_mocker.
DATA mo_alert_processor TYPE REF TO /leos/if_flight_alert_process .
DATA mo_alert_processor_mocker TYPE REF TO /leos/if_mocker.

DATA mo_system_under_test TYPE REF TO /leos/cl_flight_observer.

PRIVATE SECTION.
METHODS setup.
METHODS teardown.
METHODS test_no_alert FOR TESTING.
METHODS test_with_alert FOR TESTING.
ENDCLASS. "lcl_test_observer DEFINITION

*----------------------------------------------------------------------*
* CLASS lcl_test_observer IMPLEMENTATION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_test_observer IMPLEMENTATION.
METHOD setup.
DATA lo_mocker_method TYPE REF TO /leos/if_mocker_method.
* create the flight information backend
mo_is_in_time_mocker = /leos/cl_mocker=>/leos/if_mocker~mock( iv_interface = '/LEOS/IF_IS_IN_TIME_INFO' ).
lo_mocker_method = mo_is_in_time_mocker->method( 'GET_DELAY' ).
lo_mocker_method->with( i_p1 = 'LH' i_p2 = 402 i_p3 = '20121109' )->returns( 100 ).
lo_mocker_method->with( i_p1 = 'LH' i_p2 = 402 i_p3 = '20121110' )->returns( 5 ).
* this call creates the flight information mockup
mo_is_in_time_access ?= mo_is_in_time_mocker->generate_mockup( ).

* create an empty alert backend (we just need to track the number of method calls)
mo_alert_processor_mocker = /leos/cl_mocker=>/leos/if_mocker~mock( iv_interface = '/LEOS/IF_FLIGHT_ALERT_PROCESS' ).
* this call creates the alert processor mockup
mo_alert_processor ?= mo_alert_processor_mocker->generate_mockup( ).

* create the flight observer which is subject to this test
CREATE OBJECT mo_system_under_test
EXPORTING
io_alert_processor = mo_alert_processor
io_in_time_access = mo_is_in_time_access.
ENDMETHOD. "setup
METHOD teardown.
ENDMETHOD. "teardown
METHOD test_no_alert.
mo_system_under_test->observe_flight( iv_carrid = 'LH' iv_connid = 402 iv_fldate = '20121110' ).
cl_aunit_assert=>assert_initial( mo_alert_processor_mocker->has_method_been_called( 'ALERT_DELAY' ) ).
ENDMETHOD. "test_no_alert
METHOD test_with_alert.
mo_system_under_test->observe_flight( iv_carrid = 'LH' iv_connid = 402 iv_fldate = '20121109' ).
cl_aunit_assert=>assert_not_initial( mo_alert_processor_mocker->has_method_been_called( 'ALERT_DELAY' ) ).
ENDMETHOD. "test_with_alert
ENDCLASS. "lcl_test_observer IMPLEMENTATION

Conclusion

This was only a simple example. But even this example showed how easy about 40% of code required to mock objects could be saved. What about real world scenarios with even more complex dependencies and behaviour? In fact, 40% of coding can be saved also in these projects, often even more. Most objects can be easily mocked using a mocking framework if they are really treated as a black box.

What a good mocking framework should also provide, is mocking of specific classes using inheritance. Also, exceptions might need to be registered for specific inputs.

Currently, the described mocking framework is still under development. It supports mocks based Interfaces, method call observation and raising of exceptions but not yet the mocking based on already existing classes. However, it is in productive usage in our projects and has already been extremely valuable for making unit tests simpler and easier to implement.

Further blogs may focus on how to manage dependencies in big development projects and how dependency injection might work in ABAP using an IoC Container. Stay tuned.

Advertisements

2 thoughts on “Unit Tests in ABAP using a mocking framework

  1. Pingback: Stories about Repositories and Number Ranges | Uwe Kunath's Blog

  2. Pingback: Uwe Kunath's Blog | mockA released – a new ABAP Mocking Framework

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s