Skip to Content

Note: Don’t miss the Session on Task-Driven Development in ABAP at TechEd 2010. 

Breaking dependencies on external code and resources is one of the keys to successful test-driven development. (A dependency, loosely defined, is the reliance of your program upon another program or resource. In common usage it also refers to the  ‘depended-on component’ itself, as in ‘injecting a dependency’. Injecting or breaking a dependency simply means ‘making it possible to substitute a test entity for a depended-on component when doing unit testing’.)

If the code under test in unit testing contains too many unbreakable dependencies, then the unit test becomes meaningless. Or at least, it is no longer a test of the discrete unit of code under test. You don’t have the control to test the response of your code to different behaviors of the dependency. And your unit tests are no longer reliable tools for driving and validating your development work.

In NetWeaver 7.0 EHP1 (SAP_BASIS 7.01), the strategy for managing dependencies in ABAP is like this:

    • You need to ensure that ABAP objects in your code can be replaced with test doubles when ABAP Unit tests are running. That is, the dependencies represented by these objects must be ‘injectable’, so that the production object can be swapped out for a test object when testing.
    • You need to encapsulate non-ABAP Object dependencies such as the following in local or global ABAP classes:
      • Database operations
      • Calls to function modules and executable programs
      • Interaction with user interfaces
      • Authorization checks (the ABAP AUTHORITY-CHECK statement)
      • Use of files, system fields, and other resources.

For example, your code should not use the database or call a function module directly, but only by way of an encapsulating ABAP object. The object uses the database or calls a function for you.

You need to ensure that these ABAP objects as well are ‘injectable’, so that the dependencies encapsulated by these objects can be broken for testing.

A Pattern for Constructor Injection

Constructor injection is a common pattern for breaking dependencies in all kinds of object-oriented languages. What could constructor injection look like in ABAP coding?

In constructor injection of dependencies, you do not create a needed ABAP object in the code under test. Instead, you pass the object to the code under test as an argument of the CONSTRUCTOR method of the class.

In your ABAP Unit test methods, you can then substitute test doubles when you create the test object.

Here is what this pattern might look like in ABAP.

*————————————————*
* In CLASS_UNDER_TEST…
*————————————————*

*————————————————*
* The CONSTRUCTOR sets the private class
* variable MY_DEPENDENCY to reference
* the object of MY_INJECTED_DEPENDENCY.
*————————————————*
CLASS class_under_test DEFINITION CREATE PUBLIC.
PUBLIC SECTION.
METHODS constructor
IMPORTING my_injected_dependency TYPE REF TO if_my_dependency.
METHODS method_under_test
RETURNING
value(useful_value) TYPE i.
PRIVATE SECTION.
DATA my_dependency TYPE REF TO if_my_dependency.
ENDCLASS.

CLASS class_under_test IMPLEMENTATION.
METHOD constructor.
my_dependency = my_injected_dependency.
ENDMETHOD.

*————————————————*
* Method_under_test uses the dependency.
*————————————————*
METHOD method_under_test.
useful_value = me->my_dependency->calc_value( ).
ENDMETHOD.
ENDCLASS.

*————————————————*
* In the local ABAP Unit test classes –
* Goto -> Local Test Classes…
*————————————————*

*————————————————*
* There is a local test double class
* that reimplements IF_MY_DEPENDENCY
* for use in testing.
*————————————————*
CLASS ltd_test_double DEFINITION.
PUBLIC SECTION.
INTERFACES if_my_dependency.
ENDCLASS.
CLASS ltd_test_double IMPLEMENTATION.
METHOD if_my_dependency~calc_value.
r_value = 1.
ENDMETHOD.
ENDCLASS.

*————————————————*
* In the ABAP Unit test class, the test
* method creates a test double for
* MY_INJECTED_DEPENDENCY and creates
* the test object using the double.
*————————————————*
CLASS ltc_abap_unit_tests DEFINITION FOR TESTING
“#AU RISK LEVEL HARMLESS
“#AU DURATION SHORT.
PRIVATE SECTION.
METHODS: test_method_under_test FOR TESTING …
ENDCLASS.

CLASS ltc_abap_unit_tests IMPLEMENTATION.
METHOD test_method_under_test.
DATA cut TYPE REF TO class_under_test,
test_double TYPE REF TO ltd_test_double,
useful_value type i.
CREATE OBJECT test_double.
CREATE OBJECT cut
IMPORTING
my_injected_dependency = test_double.

useful_value = cut->method_under_test( ).

cl_abap_unit_assert=>assert_equals(
act   = useful_value
exp   = 1  ).
ENDMETHOD.
ENDCLASS.

 

Here is how the sample code works:

    • Instead of creating depended-on objects (dependencies) inline in its code, CLASS_UNDER_TEST in the sample above imports depended-on objects with its CONSTRUCTOR method. This means that CLASS_UNDER_TEST can use separate production and test versions of its dependencies.
    • A local helper class – LTD_TEST_DOUBLE in the sample code – provides a test version of the IF_MY_DEPENDENCY object used by CLASS_UNDER_TEST.
  • In the ABAP Unit test class – LTC_ABAP_UNIT_TESTS in the sample code – the test method creates an instance of the test double. It also creates an instance of the CLASS_UNDER_TEST for use in the test methods in LTC_UNIT_TESTS.An alternative to cluttering up the test method with the instantiation of the test double and test object would be to do these tasks in the ABAP Unit SETUP method, which runs automatically before each test method.  You could also cleanup the ‘test fixture’ with the ABAP Unit TEARDOWN method, which automatically runs after each test method.  You just need to declare and implement these methods (or have Test Code Generation do part of this task for you).
  • When the ABAP Unit test methods in LTC_ABAP_UNIT_TESTS run, they use an instance of CLASS_UNDER_TEST that runs with a test double of IF_MY_DEPENDENCY.In this example, the test double functions as a ‘responder’, a mock or fake. It only returns a reliable test value. But you could easily make an ‘observer’ out of the test double by adding verification methods to it. A test method could then check for example the indirect output, or the data that the code under test provides to the test double.

The ABAP Workbench and the Test-Driven Development Cycle in EHP1 is the first part of this weblog.

To report this post you need to login first.

5 Comments

You must be Logged on to comment or reply to a post.

  1. Alisdair Templeton
    Hi Stephen.

    It’s fantastic to see others within the SAP community adopting these approaches to their development. I’ve been advocating TDD to anyone that will listen, and although the current AUnit tool is not perfect (as you discussed in your first blog), with some tweaks you can certainly do TDD and reap the benefits.

    I’ve just been involved in a project where we built Enterprise Services to distribute HR Data. We used Constructor Injection to inject either a wrapped HRPA Masterdata Class (we abstracted the interface to something more intention revealing), or a test class with dummy data. This allowed us to test the extractor mappings etc without worrying about the moving target of a Development Database.

    I’ve also been able to take this a step further using the dynamic code generation capability of ABAP and build a very basic Mocking framework (loosely based on the functionality of RhinoMock). With this I’ve been able to test everything from HR Extractors to ICF HTTP Request Handlers, where the Server, Request and Response Objects can easily be mocked allowing the testing to focus purely on the Handler.

    Keep up the good work and I look forward to your session at Tech Ed!

    Al Templeton.

    (0) 
    1. Stephen Pfeiffer Post author
      Hi! 

      Thanks – the ABAP apostles of TDD are really Michael Gutfleisch and Yekaterina Zavozina (among others), who will be holding the TechEd presentation. 

      Why not document your mocking framework here?

      Cheers, Stephen

      (0) 
      1. Alisdair Templeton

        Hi Jeroen.

        Apologies for taking so long to get back to you!

        I’m still using parts of the framework, though its development hasn’t moved far since I commented on Stephens blog. I spoke on TDD at the Mastering SAP Technologies conference in Sydney this year, and will also be presenting on TDD at Tech Ed in Vegas, so my interest in TDD is still strong! Maybe see you there?

        The framework isn’t in a state to share as yet – that said, it’s basically building local classes at run-time based on the interface of the object to mock. I followed the “fluent” style of Rhino mocks fairly closely. I’d be planning on demonstrating some form of this as part of my presentation in October, so hopefully I’ll have something worthy of the code exchange by then.

        In the mean time, if you have any questions I’m happy to help.

        AT

        (0) 
  2. Christian Drumm
    Hi Stephen,

    nice blog.

    I just wanted to mention a useful little addition. In the context of ABAP development I often encountered the situation that you can’t really control how a custom class is instantiated (e.g. it is called by a framework based on some customizing). In this situation I usually define the dependencies as optional parameters of the constructor. In the constructor I then check if the optional parameter has been supplied. If yes the injected object is used, otherwise the constructor creates the required objects itself.

    This way it is possible to inject dependent objects while running unit test but also use the class in the framework it was build for.

    Christian

    (0) 

Leave a Reply