Introduction

The Goals of Test Automation at XUnitPatterns.com include Keywords like “robust”, “repeatable” “fully automated” and so on. In ABAP you can simply use Database Access using the OpenSQL Statements, wich maybe scattered all over your Code. Unit Testing with Database Access in General is a Problem, because you easy miss those goals.

For blackbox tests you need a Constant given State as Precondition for the Test and your Test should run as quickly as possible. For repeatable Test’s this would mean that you may have to flush Tables, insert a consistent State, run your Test and finally rollback the LUW – and hope that you have replaced all dependency’s that might have executed an explicit COMMIT WORK Statement. Apart from messy Setup Code you may suffer Performance Problems executing your Tests. 

Let’s do one step back – what is your goal? We have to our code in several Layers, with different techniques. On the lowest level you start testing your classes in isolation, and then start to test upwards with classes in combination, a complete subsystem or End-To-End with GUI and so on.


Tesing the logic of a Class without relaying on the Database can be simply archieved using local classes. I originally found this Idea in Rüdiger Plantiko’s Wiki Article ABAP Unit Best Practices. The Idea is to encapsulate all SQL statements using a local Class. Before you run a unit test you have to replace the lcl_db Instance with an Instance which does not access the database – a so called stub. The Stub returns a Structure or internal table defined by the test.


Advantages

  • Placing all the SQL statments in a local class gives you inside the class fever points of change for your database logic. Also you may reduce the number of statements, because you get a good overview of your class’es SQL Statements. 
  • You can test different execution path’s of your class under test by returning different data

Disadvantages

  • To inject a local class stub instance you have to have access to the private Instance Attribute.
  • Navigating to the local class implementations using SE80 may be confusing for collegues
  • You cannot execute DB queries in your constructor if the construcor is not private (which is in general not a good idea)


This approach can also be used encapsulation Function Modules using an lcl_api class.



How to use it

Step 1: The Class under Test

In your global Class Overview you navigate to the class local definitions using the Short-Keys [CTRL]+[F5]. In this Include I define the Interface lif_db and the classes lcl_db and lcl_db_stub.


INTERFACE lif_db.

   METHODS:
     get_ztb_test_1
       IMPORTING
         i_land1 TYPE land1
       RETURNING value(rs_ztb_table_1) TYPE ztb_table_1.

 ENDINTERFACE.

 CLASS lcl_db DEFINITION  FINAL.

   PUBLIC SECTION.
     INTERFACES lif_db.

 ENDCLASS.
 CLASS lcl_db_stub DEFINITION  FINAL.

   PUBLIC SECTION.

     DATA:
       ms_ztb_table_1__to_return  TYPE ztb_table_1.

     INTERFACES lif_db.

 ENDCLASS.



Then you go back the the global class definition and jump to the local class definition using [CTRL]+[SHIFT]+[F6].


CLASS lcl_db IMPLEMENTATION.

   METHOD lif_db~get_ztb_test_1.

     SELECT SINGLE *
       FROM
         ztb_table_1
       INTO
         rs_ztb_table_1
       WHERE
         land1 = i_land1.

   ENDMETHOD.

 ENDCLASS.
CLASS lcl_db_stub IMPLEMENTATION.

   METHOD lif_db~get_ztb_test_1.

     rs_ztb_table_1 = me->ms_ztb_table_1__to_return.

   ENDMETHOD.

 ENDCLASS.


The next Step is to add the Database Instance to your primary Class. Add an Member-Attribut in the Private Section:



mo_db TYPE REF TO lif_db.


In the Constructor you have to instantiate it.



CREATE OBJECT me->mo_db
     TYPE REF TO lcl_db.


In your Class with the production Code you can access the Database by calling the methods of mo_db.

Step 2: The Test-Class

Now let’s have a look at the Test-Class. I don’t use setup the generate Test Instances, normally i have a get_fcut Method, that returns the Instance to Test.


CLASS ltcl_test_my_class DEFINITION
     FOR TESTING
          DURATION SHORT
          RISK LEVEL HARMLESS
          FINAL.
PRIVATE SECTION.
METHODS:
       get_fcut
IMPORTING
           i_for_vkorg TYPE vkorg
         RETURNING VALUE(ro_fcut) TYPE REF TO zcl_encapsulated_db_access_1,
       get_db_stub
IMPORTING
           i_for_vkorg TYPE vkorg
         RETURNING value(ro_db_stub) TYPE REF TO lcl_db_stub,
       run_a_test FOR TESTING.
ENDCLASS.

Between Definition and Implementation you have to make the local Test-Class a friend of the Class under Test. That’s necessary to access the private mo_db Instance and replace it with the Stub. Whenever possible you should use other techniques for Dependency-Injection.


CLASS zcl_encapsulated_db_access_1 DEFINITION LOCAL FRIENDS
       ltcl_test_my_class.

Resist the temptation the use any other “internal” private Attributes oder Methods – knowing the Internals of the Class you’re testing is not a good Idea.


CLASS ltcl_test_my_class IMPLEMENTATION.
METHOD get_fcut.
CREATE OBJECT ro_fcut.
     ro_fcut->mo_db = me->get_db_stub( i_for_vkorg ).
ENDMETHOD.
METHOD get_db_stub.
CREATE OBJECT ro_db_stub.
" Setup Stub Values
     ro_db_stub->ms_ztb_table_1__to_return-vkorg = i_for_vkorg.
ENDMETHOD.
METHOD run_a_test.
ENDMETHOD
ENDCLASS.

That’s it!

Resume

Building your code this way allows you the test the Logic inside the class without the Database itself. With Parameterised setup oder get_fcut Methods you can test multiple Execution Path’s in your Logic. 
But be aware: Sometimes it’s a narrow Path between a good Test with good Test coverage and complicated and messy Test’s that get brittle overt time and complicate Changes instead as acting as a safety net.

Even if I don’t unit Test the Class I tend to extract the SQL Queries in an “db” class. That allows my to hide the actual query behind an expressive Method Name.

To report this post you need to login first.

3 Comments

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

  1. Naimesh Patel

    Nice Article Stefan.

    You may not need create the stub, if you follow test double mentioned in this article ABAP Test Double Framework – An Introduction (I haven’t tried it yet, though).


    It would be always helpful to have a separate data selection class, so you can supply different data sources into the same object. (DB selection and/or Archive selection). And as you have pointed out through out this article, it would be helpful to implement dependency injection.

    Thanks,
    Naimesh Patel

    (0) 
    1. Stefan Mehnert Post author

      Thanks for your Feedback Naimesh!

      I’m really looking forward to work on a 7.4, the new Features are very interesting, I experimented with Uwe Kunath’s mockA Framework and other ways of using Test Doubles.

      More about that soon 🙂

      Thanks & Best Regards

      Stefan Mehnert

      (0) 
  2. Gabor Farkas

    I generally use a similar approach but always declare the DB interface and the DB class globally – I don’t really see any advantages in making the class local to be honest.

    You can then use contructor injection (which makes the dependencies more clear) instead of having the test class change the private attribute, you’ll have better SE80 support, and the DB class can be reused elsewhere. (Even if you think the DB calls are very specific to that particular class, it is still a good idea to make it resuable.)

    (0) 

Leave a Reply