Managing Dependencies in TDD: Private Injection in ABAP in EHP1
Don’t miss the presentation on Test-Driven Development in ABAP at TechEd 2010!
At design time or when you refactor code, you can enable better unit testing by allowing dependencies to be injected into your code. Your ABAP Unit tests can then substitute test doubles for the production entities wherever you have enabled dependency injection.
This weblog shows how you might set up private injection of dependencies in ABAP. This is a useful pattern if you do not want to expose critical dependencies to users of a class, as you might do with Managing Dependencies in TDD: Constructor Injection in ABAP in EHP1.
Private Dependency Injection
If you declare a local test class to be a LOCAL FRIEND of the class under test, then the test class can access protected and private components of the class. This is useful if the test class should test private and protected methods. But it also allows the test class to inject dependencies that are declared as private attributes of the class under test.
Private injection by a local friend allows you to break dependencies but still protect them from manipulation from outside the class under test.
Here is some sample code showing what private injection might look like:
* In CLASS_UNDER_TEST…
* The dependency needed by
* CLASS_UNDER_TEST is instantiated
* in the CONSTRUCTOR method. The
* constructor sets the private variable
* MY_DEPENDENCY to reference the
* depended-on object.
CLASS class_under_test DEFINITION CREATE PUBLIC.
value(useful_value) TYPE i.
DATA my_dependency TYPE REF TO if_my_dependency.
CLASS class_under_test IMPLEMENTATION.
CREATE OBJECT my_dependency
TYPE REF TO cl_my_dependency.
useful_value = my_dependency->calc_value( ).
* In the local ABAP Unit include
* Goto -> Local Definitions/
* Implementations -> Local Test
* 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.
* LTC_ABAP_UNIT_TESTS is declared as
* a LOCAL FRIEND of the class under
* test. As a LOCAL FRIEND, the test
* class can access protected and
* private members of CLASS_UNDER_TEST.
CLASS ltc_abap_unit_tests DEFINITION DEFERRED.
CLASS class_under_test DEFINITION
LOCAL FRIENDS ltc_abap_unit_tests.
* In LTC_ABAP_UNIT_TESTS, the test
* method substitutes a test double for
* the production object referenced
* by the private attribute MY_DEPENDENCY
CLASS ltc_abap_unit_tests DEFINITION FOR TESTING
“#AU DURATION SHORT
“#AU RISK LEVEL HARMLESS
cut TYPE REF TO class_under_test.
METHODS: calc_value_test FOR TESTING.
CLASS ltc_abap_unit_tests IMPLEMENTATION.
DATA: useful_value TYPE i,
test_double TYPE REF TO ltd_test_double.
CREATE OBJECT test_double.
CREATE OBJECT cut.
cut->my_dependency = test_double.
useful_value = cut->method_under_test( ).
act = useful_value
exp = 2 ).
Here is how the sample code works:
- Instead of creating depended-on objects (dependencies) inline in its code, CLASS_UNDER_TEST in the sample does the following:
- It declares dependencies as PROTECTED or PRIVATE attributes.
- It instantiates these depended-on objects outside the methods that actually use them. In this case, the CONSTRUCTOR does the instantiation.The instantiation of depended-on objects is hidden from users of CLASS_UNDER_TEST.
- 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, and substitutes the test double for the production MY_DEPENDENCY object for use in testing.
An alternative to cluttering up the test method with the instantiation of the test double and CUT test object would be to do these tasks in the ABAP Unit SETUP and TEARDOWN tasks.