Skip to Content

Hi friends,

As far as I know test isolation is widely used in SAP internal to build unit test code, at least in my team. Test isolation in unit test means that in your production code you make use of some API(class method/Function module) which are developed by other team, and you would not like to really consume them during your unit test, since you would not accept that most red lights in your unit test report are caused by the bugs in those external API,right? Instead, you expect that the call of those external API during your unit test execution will be replaced by some dummy code written by yourself.

I will show you four different ways to achieve that.

The example comes from one productive class in my project. For simplicity reasons I don’t list unrelevant code here. The class is ZCL_DESTRUCTION_IMG_TOOL_S1.

/wp-content/uploads/2013/11/clipboard1_328310.png

The main logic is doing a cleanup work: loop every work list item, check whether it has some dependent object. If so, delete it.

 method RUN.
    DATA: lv_social_post_id TYPE string.
    fill_worklist( ).
    LOOP AT mt_worklist INTO lv_social_post_id.
       IF dependent_object_existed( lv_social_post_id ) = abap_false.
          delete( lv_social_post_id ).
       ENDIF.
    ENDLOOP.
  endmethod.

The worklist is just hard-coded:

method FILL_WORKLIST.
     CLEAR: mt_worklist.
     APPEND '20130001' TO mt_worklist.
     APPEND '20130002' TO mt_worklist.
     APPEND '20130003' TO mt_worklist.
  endmethod.

The reason why test isolation is used in our unit test is because in method dependent_object_existed, we call an API provided by another team, and we don’t want that API to be really executed during our unit test execution. For demonstration reason, I use the following code to simulate the production code.

It means during the unit test on this class, the following code is NOT expected to be executed at all.

  method DEPENDENT_OBJECT_EXISTED.
     WRITE: / 'Productive code to check dependent object existence for ID: ' , iv_social_post_id.
  endmethod.

Approach1: test subclass instead

1. Change the visibility of method DEPENDENT_OBJECT_EXISTED from private to protected. The idea is in this approach, we create a sub class ( a local test class) which inherits from ZCL_DESTRUCTION_IMG_TOOL_S1. Since the DEPENDENT_OBJECT_EXISTED is now protected, we have the chance to redefine it in the local test class.

/wp-content/uploads/2013/11/clipboard2_328311.png

/wp-content/uploads/2013/11/clipboard3_328312.png

The mock code is simple like below:

  METHOD dependent_object_existed.
     WRITE: / 'Test mock code to check dependent object existence for ID: ' , iv_social_post_id.
  ENDMETHOD.

And the code for method start:

  method start.
    f_cut = lcl_Destruction_Test=>get_sub_instance( ).
    f_cut->run(  ).
endmethod.

In this approach, actually the class being unit test are not lcl_destruction_test, instead of the original class ZCL_DESTRUCTION_IMG_TOOL_S1.

/wp-content/uploads/2013/11/clipboard4_328313.png

when execution comes into run method, since the subclass only redefines the very method dependent_object_existed, so the execution of the left methods are still using the code of ZCL_DESTRUCTION_IMG_TOOL_S1. That means all methods of ZCL_DESTRUCTION_IMG_TOOL_S1 except dependent_object_existed will be covered by unit test.

/wp-content/uploads/2013/11/clipboard5_328314.png

And when execution comes into method dependent_object_existed,since currently “me” points to the local test sub class, so the mock code will be executed, which is just what we expected.

/wp-content/uploads/2013/11/clipboard6_328315.png

The limitation of this approach is, if the author of class ZCL_DESTRUCTION_IMG_TOOL_S1 insists on that it should be defined as final, it is impossible to define any sub class of it.

Approach2 – interface extraction + optional argument

The idea is to extract the logic written in method dependent_object_existed and put its implementation into method dependent_object_existed of a new interface ZIF_SOC_DEPENDENCY_DETECTOR instead. Via such abstraction, the loose coupling of dependency detection call and its implementation is achieved.

Necessary change on production class ZCL_DESTRUCTION_IMG_TOOL_S2 source code:

1. Private method dependent_object_existed can be removed now.

/wp-content/uploads/2013/11/clipboard7_328319.png

2. Create a local class to implement the productive logic of dependency detection as usual:

/wp-content/uploads/2013/11/clipboard8_328320.png

3. Create an optional argument of method run.

/wp-content/uploads/2013/11/clipboard9_328321.png

4. In method run, if no reference is passed in for io_dep_obj_detector, the default one for production use will be called (line5).If there is indeed one dependent object detector passed in, use that one. (line7)

Also the previous method dependent_object_existed is removed; the interface method call is used instead.

/wp-content/uploads/2013/11/clipboard10_328322.png

5. The unit test class also implements interface method dependent_object_existed.In unit test code, the optional parameter of method run will be filled, so that the redefined dependency detection logic implemented in interface method in unit test code will be called instead ( according to the logic in step4, line 7)

/wp-content/uploads/2013/11/clipboard11_328323.png

Now the possibility is given for consumers to provide their own dependency determination logic by implementing interface and filling the optional parameter of method run; however this is not what we expected in the beginning.

Signature of method run is changed, which is a non-critical change. (Adding a new optional method parameter is not an incompatible change)

In my opinion it could not be regarded as a 100% test isolation solution.

Approach3 – Dynamic detector initialization

Interface extraction is also necessary for this solution.

Necessary change on production class ZCL_DESTRUCTION_IMG_TOOL_S3’s source code:

1. Create a local class to implement the productive logic of dependency detection just the same as approach2.

2. Add a new static member attribute for technical name of dependency class name. Default value is local class name created in step1:

/wp-content/uploads/2013/11/clipboard12_328324.png

3. Initialize the detector instance according to its name maintained in member attribute mv_detector_type_name.

Since its default value is the local class LCL_PROD_DEP_DETECTOR, so in the runtime the productive detection logic will be called.

/wp-content/uploads/2013/11/clipboard13_328328.png

4. Define the unit test class as the friend of class ZCL_DESTRUCTION_IMG_TOOL_S3, so that it can alter even the private attribute mv_detector_type_name of ZCL_DESTRUCTION_IMG_TOOL_S3.

/wp-content/uploads/2013/11/clipboard14_328329.png

In the unit test code, just add one line( line 62 ). Now the detection logic redefined in unit test class will be called.

/wp-content/uploads/2013/11/clipboard15_328330.png

Approach3 is superior compared with approach2 in that no method signature change is required. Class consumer will have no chance to pass their own detection logic into class in a normal way. However, a new class member attribute is introduced, which could be not necessary in the next approach 4.

Approach4 – detector reference replacement

Similar with approach 3 to some degree.

Necessary change on production class ZCL_DESTRUCTION_IMG_TOOL_S4 source code:

1. Implement the productive dependency detection logic in local class lcl_prod_dep_detector just the same as approach3.

2. Initialize the productive detector instance in CONSTRUCTOR.

  method CONSTRUCTOR.
    CREATE OBJECT mo_dep_obj_detector TYPE lcl_prod_dep_detector .
  endmethod.

3. Implement the interface method in test class.

4. Define unit test class as friend of ZCL_DESTRUCTION_IMG_TOOL_S4.

5. In unit test code, replace the instance of dependency detector with unit test class reference itself, so that our own logic would be called in unit test:

/wp-content/uploads/2013/11/clipboard16_328331.png

No new attribute or method interface change is required in this solution. This solution should be used for test isolation whenever possible.

You can find the source code of these four approaches from here.

To report this post you need to login first.

7 Comments

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

  1. Paul Hardy

    This is very interesting.

    Have you had a look at the ABAP unit mocking framework MOCKA being developed as an open source project by Uwe Kunath?

    https://github.com/uweku/mockA

    I would be interested in your thoughts on this.

    It is good that so many people are thinking about the easiest / fast / best way to do unit tests. Mind you, I have a horrible feeling the vast majority of ABAP programmers don;t even know what a Unit Test is.

    i have tried to explain it to my colleaguse at my company and they look at me like I am mad.

    Cheersy Cheers

    Paul

    (1) 
    1. Jerry Wang Post author

      Hi Paul,

      I will try that soon. Thank you for your notification. At least in my team we follow the test drive development style.

      Best regards,

      Jerry

      (0) 
      1. Paul Hardy

        For the first seven years I was an accountatnt – then along came SAP and all around the world I went.

        I was reading your blog on the train again and as far as I can see the argument for having an instance variable holding the reference to the external system to be mocked, as opposed to having that reference as an optional parameter in the constructor, is that if it is an optional parameter then any calling program can inject a fake object.

        The funny thing is, constructor injection can serve two purposes at once. The obvious one is unit tests, but the other example I have encountered is thus,

        You have a class where you pass in the database access layer as an optional parameter in the constructor. In real life nothing is passed in, the real database access object is used. In my unt test I pass in a mock object.

        Now, the user requirements change – shock – as well as doing the business logic based on what is actually in the database, they want a simulation option where they want to know what woud happen if the contents of the database were different.

        Do i have to re-write everything? No, I just give them an input screen to enter their “what-if” values, and pass those in as an object wit the same interface as the original database class. I don;t need to change a line of the original method, the good old “open-closed” principla would be dancing with joy.

        I am doing such a thing right now in my real job.

        Does that make any sense?

        Cheersy Cheers

        Paul

        (0) 
  2. Brian McDonnell

    Good read. I’ve been using dependency injection for some time now to control which web services are used by a custom transaction. I was always planning on applying a similar strategy to help with unit testing and after reading through approaches 3 & 4 it appears to be a good idea.

    Thanks for sharing.

    (0) 
  3. Steve Mo

    Hi Jerry,

    Interesting post.

    Actually what you are concluding could be how to inject test double to production code. The Class type name give me another view of injection although it’s not necessary as you said but it’s a good try. Just want to notify there is still a “Setter” injection way for your choice. It’s really simple that keep constructor clean and use set_XXXX method to inject the doubles.

    BR,

    Steve

    (0) 
  4. Gabor Farkas

    Nice article.

    I think the first approach (subclass and override) is a relatively painless way of making legacy code testable so it’s definitely useful.

    For new stuff something along the lines of approach 4 seems to be the way to go because it forces you into better design, ie. creating a lot of small classes with clear responsibilities (separation of concerns).

    In my opinion constructor injection is _generally_ better than field injection because it makes dependencies explicit and doesn’t hide them. Of course this requires the interfaces to be global. Then you could delegate the creation of the class to a factory that sets up the dependencies and wires them together to create your object. (Again, separation of concerns.)

    (0) 

Leave a Reply