Small adventure in Testing – RTTS, Injection and Constraints
This is on a 7.31 system, so not all the goodies available later I can use just yet…
I have a class (ZCL_MY_HANA_CLASS) that I want to write ABAP Unit Tests for. Although my development system is 7.31, this particular program is for use with a HANA system, so I can’t test directly in 7.31 – I have to transport it to my HANA system for testing.
But it’s nice to be able to test in 7.31, so I’ve designed a framework using interfaces where I access the >7.31 objects dynamically, and in 7.31 itself I’ve a pseudo HANA implemented using Z tables.
To make all this work, I’ve a static attribute called _IMPLEMENTOR (type string) created in the CLASS-CONSTRUCTOR method. (All my static methods and attributes begin with an underscore, so they’re readily recognisable).
" Standard HANA class exists IF zcl_exist=>class_exist( c_dbms_class-sap_std ) EQ abap_true. _implementor = c_dbms_class-productive. " Standard doesn't exist, see if a test class is available ELSEIF zcl_exist=>class_exist( c_dbms_class-test ) EQ abap_true. _implementor = c_dbms_class-test. ENDIF.
In my _GET_INSTANCE method I have
DATA implementor TYPE REF TO zif_hana_class. CREATE OBJECT implementor TYPE (_implementor_name) EXPORTING i_dbcon = i_dbcon.
Here, ZIF_HANA_CLASS is an interface that looks just like the public section of an standard SAP HANA related class in 7.40. Just for completeness, there are two implementation of ZIF_HANA_CLASS – one is a Z class that dynamically passes through the calls to the 7.40 class in a 7.40+ system. The other is a Z test class that writes/reads from my Z test tables in 7.31.
I want to get the method _GET_INSTANCE into a test harness, with minimal refactoring. _IMPLEMENTOR is a private attribute, so I need to give my test class access to it.
Via ADT/Eclipse this is done in the Class-relevant local types tab.
CLASS ltc_handler DEFINITION DEFERRED. CLASS zcl_my_hana_class DEFINITION LOCAL FRIENDS ltc_handler.
In my test class (ltc_handler), I can now have.
me->cut->_implementor = ???.
But what do I assign _IMPLEMENTOR? What value should it have. I want to use neither my Z test class, nor the SAP class (if it exists!). The obvious answer is a local test double.
CLASS ltd_hdb DEFINITION. PUBLIC SECTION. INTERFACES zif_hana_class. DATA db TYPE HASHED TABLE OF ... WITH UNIQUE KEY user_name. ENDCLASS.
The thing is, usually when I have a test double I use object instances to inject. But here I have only the name of the class. So what is the name of ltd_hdb that the CREATE OBJECT statement will recognised?
This is the final bit of the jigsaw. RTTS is to the rescue.
CLASS ltd_hdb DEFINITION. PUBLIC SECTION. INTERFACES zif_hana_class. DATA db TYPE HASHED TABLE OF ... WITH UNIQUE KEY user_name. CLASS-METHODS: _get_my_abs_type_name RETURNING value(r_name) TYPE string. ENDCLASS. CLASS ltd_hdb IMPLEMENTATION. ... METHOD _get_my_abs_type_name. DATA one_of_me TYPE REF TO ltd_hdb. CREATE OBJECT one_of_me EXPORTING i_dbcon = ''. r_name = cl_abap_classdescr=>get_class_name( one_of_me ). ENDMETHOD. ENDCLASS.
To inject the class name, all I have to do in my test class SETUP method is have
me->cut->_implementor_name = ltd_hdb=>_get_my_abs_type_name( ).
Now, when I test my code, I’ve full control of what the implementor instance does, and know I will have no unexpected side effects.
I got to my solution through a combination of wikis, reading the ABAP documentation and searching through already asked questions. All in the sap.com domain.
Apparently, this is quite a rare skill… :-D.
While writing my unit tests, I wanted to test that two instances of an object were not the same object. ASSERT_EQUALS is great for checking that they are the same, but there is no ASSERT_UNEQUALS. There’s ASSERT_DIFFERS, but that only works with elementary object types.
ASSERT_THAT looks promising, but I’ve never used, so of to search I go. Fairly quickly, I find that I need an implementaion of IF_CONSTRAINT. This has two methods:
methods IS_VALID importing !DATA_OBJECT type DATA returning value(RESULT) type ABAP_BOOL . methods GET_DESCRIPTION returning value(RESULT) type STRING_TABLE .
IS_VALID does the actual tests. GET DESCRIPTION returns the message that you get if the test fails.
And this is how it works. First, the implementation of my constraint. I’ve gone for an LTA prefix as “Local test assert constraint”. (LTC is already taken as Local test class).
CLASS lta_not_identical_objects DEFINITION FOR TESTING. PUBLIC SECTION. INTERFACES if_constraint. TYPES: BEGIN OF two_objects, object1 TYPE REF TO object, object2 TYPE REF TO object, END OF two_objects. ENDCLASS. CLASS lta_not_identical_objects IMPLEMENTATION. METHOD if_constraint~is_valid. FIELD-SYMBOLS <both_objects> TYPE two_objects. ASSIGN data_object->* TO <both_objects>. IF <both_objects>-object1 NE <both_objects>-object2. result = abap_true. ENDIF. ENDMETHOD. METHOD if_constraint~get_description. INSERT `Those instances were supposed to be not the same object` INTO TABLE result. ENDMETHOD. ENDCLASS.
And this is how I use it. Note again this is a 7.31 system, so it’s a little verbose…
DATA not_the_same TYPE REF TO if_constraint. CREATE OBJECT not_the_same TYPE lta_not_identical_objects. DATA both_objects TYPE lta_not_identical_objects=>two_objects. both_objects-object1 = first_instance. both_objects-object2 = second_instance. DATA ref_of_both_objects TYPE REF TO data. GET REFERENCE OF both_objects INTO ref_of_both_objects. cl_abap_unit_assert=>assert_that( act = ref_of_both_objects exp = not_the_same ).
Of course, I did check that the constraint worked for both unequal instance and equal instance. And when bound/unbound.