Managing Dependencies in TDD: Constructor Injection in ABAP in EHP1
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.
IMPORTING my_injected_dependency TYPE REF TO if_my_dependency.
value(useful_value) TYPE i.
DATA my_dependency TYPE REF TO if_my_dependency.
CLASS class_under_test IMPLEMENTATION.
my_dependency = my_injected_dependency.
* Method_under_test uses the dependency.
useful_value = me->my_dependency->calc_value( ).
* 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.
CLASS ltd_test_double IMPLEMENTATION.
r_value = 1.
* 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.
METHODS: test_method_under_test FOR TESTING …
CLASS ltc_abap_unit_tests IMPLEMENTATION.
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
my_injected_dependency = test_double.
useful_value = cut->method_under_test( ).
act = useful_value
exp = 1 ).
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.