SAP ABAP Unit Test – How to deal with classes of static methods
One way is this from Ravirajsinh Gohil
But this is the way I do it.
Let’s say, in the mists of time, someone created a static method in a class. Now, we all know now that is a bad idea, but back then, we were young, naive and full of dreams. I remember some of my dreams. The one with the carnivorous trombones was particularly traumatic.
The class has one method:
CLASS zcl_utility DEFINITION. PUBLIC SECTION. CLASS-METHODS: is_client_open RETURNING VALUE(r_result) TYPE abap_bool. ENDCLASS. CLASS zcl_utility IMPLEMENTATION. METHOD is_client_open. IF ... r_result = abap_true. ENDIF. ENDMETHOD. ENDCLASS.
It’s naturally called by:
METHOD wot_does_something. IF zcl_utility=>is_client_open( ). ... ENDMETHOD.
But we really want to test double this, so that we can put our code in a test harness.
First create a local interface and class.
CLASS lif_utility DEFINITION. METHODS: is_client_open RETURNING VALUE(r_result) TYPE abap_bool. ENDCLASS. CLASS lcl_utility IMPLEMENTATION. METHOD is_client_open. r_result = zcl_utility=>is_client_open( ). ENDMETHOD. ENDCLASS.
In my main class I have:
...DEFINITION. PUBLIC SECTION. CLASS-METHODS class_constructor. PRIVATE SECTION. CLASS-DATA _utility TYPE REF TO lif_utility. ...IMPLEMENTATION. METHOD class_constructor. _utility = NEW lcl_utility( ). ENDMETHOD. METHOD wot_does_something. IF _utility->is_client_open( ). ... ENDMETHOD.
Now the static method call is an instance method call. And it’s a class implementing an interface, which means now we can implement a test double.
CLASS ltd_utility DEFINITION FOR TESTING. PUBLIC SECTION. INTERFACES lif_utility. ENDCLASS. ... CLASS ltc_my_test DEFINITION FOR TESTING DURATION short ... PRIVATE SECTION. DATA: utility TYPE REF TO ltd_utility, cut TYPE REF TO zmy_main_class. METHODS: setup. ... ENDCLASS. CLASS ltc_my_test IMPLEMENTATION. METHOD setup. cut = new #( ). utility = new #( ). cut->_utility = utility. ENDMETHOD. ENDCLASS.
Now, when the test methods run, instead of the lcl_utility methods being called, the ltd_utility methods will be called.
I prefer this method of injection to constructor injection. But of course, this approach will work for that as well.
Where to define the various objects
In the example, I’ve defined _utility as private. As it stands, the test class can’t see it. You could make _utility public, but public changeable attributes are not a good idea.
Within a class, there are five sections (this is most clearly seen in Eclipse).
- Global Class – the methods, attributes etc. of your class.
- Class-relevant Local Types – definitions of local classes and interfaces (but not test classes).
- Local Types – implementation of local classes and interfaces (but not test classes).
- Test Classes – test class and test doubles definitions and implementations
- Macros – do not use
In the example I would have in the Class-Relevant Local Types
INTERFACE lif_utility. ... ENDINTERFACE. CLASS lcl_utility DEFINITION. ... ENDCLASS. CLASS ltc_my_test DEFINITION DEFERRED. CLASS zmy_main_class DEFINITION LOCAL FRIENDS ltc_my_test.
In this way, my test class is a friend of the main class and so can access it’s private parts.
Other approaches are possible, but this is what I use.
As pointed out by Scott Lawton in the comments, there is a potential problem with this approach. If the constructor of the access class does some work, then that’s going to happen.
Other approaches are lazy instantiation of the access class, or as a parameter via the constructor.
My constructors in access objects are not explicitly defined, but maybe in the future someone would add one, and then my unit tests would go horribly wrong.
This approach looks good👌.
It's just that due to the fact, Local class code is not covered with Unit Tests. Overall coverage will be impacted. One has to consider individual coverage of the Main class.
I don't think you're right for two reasons.
1. You can test local class code as much as any other if you want
2. In this case, the local class code is merely a front end to external calls, and thus conceptually is identical. You're not testing external calls, you're not testing the local classes.
I use this approach because I can't touch zcl_utility. Otherwise I'd create a zif_utility interface, implement the methods and remove all the static methods. I'd refactor all classes that use zcl_utility as well. Then I can simply test double zif_utility.
I did use your approach once when I began to write unit tests, but adopted this approach because fundamentally, dynamic programming is more expensive than concreate programming - reason being that errors don't manifest until runtime. Syntax errors are cheap to fix.
It seems a bit wrong to detract from robustness in order to get code into test harness, since unit testing is about protecting the code in the first place.
I clicked "like" for the effort and the trombones.
I understand nothing. I'm still trying to understand why I must declare all classes via interfaces, much less where is the evil in static methods. They sound "natural" to me.
But I'm trying, really.
One reason to do this is to assist with writing unit tests.
For example, I often have two classes in a development - one that handles the user interface type stuff, drawing ALVs (controller), responding to double clicks, and one that handles all the database stuff, calls to function modules (data access object).
The first one has only dependencies on the second one. It doesn't reference anything else external to the class (except cl_salv stuff, obviously).
Now I want to write a unit test to ensure that zcontroller->get_data correctly transforms the dbdata for all possibilities.
I could create the data, but that can be difficult. I don't know the transactions, and I certainly don't know how to make sure all possibilites are created in the database. In DEV there probably isn't much data that I can use and the functional analyst doesn't want to create any - in any case it gets deleted every few months.
So what I do is create an interface zif_dao with the method get_dbdata, and have zdao implement it.
In the Test Classes section, I can create a test double of zif_dao.
When I write my test class, I use this test double ltd_dao instead of the real class zdao.
When the unit test runs, instead of selecting from the database, it now uses the data I set up in the method ltc_controller->test.
Ever used a function module where you have to call another one in the same group first to reset the globals?
Static methods are essentially the same as function modules. That's why they're not ideal.
With instance attributes you can be sure that nothing is left over when you create a new instance. Except static attributes which you want to avoid anyway.
TL;DR static methods hinder encapsulation. Encapsulation is good, therefore static methods are bad.
You can't redefine a static method in ABAP OO, which can be a problem.
For utility methods that do one thing, they can make sense. My experience though is that if you create a class full of static methods, sooner or later you'll regret it.
I do use static methods with factory patterns. E.g. ZMYCLASS=>GET_INSTANCE( ).
Ok, now I need a button to "like even more". You convinced me about static methods. The previous comment was too long for my brain to wholly process it (yet).
I'm not full TDD, and 'though I'm all about MVC, I don't use it for small programs, where I just create a single cl_app class with all the stuff, and don't use TDD.
When (and not "if") the day when I'll be fully TDD, I'll create global classes for everything, I guess. Programs should be something like:
I don't use TDD either. But I still write tests when I need to. A couple of scenarios.
I had the second one recently. One customer was getting a time out with a particular feature when run on a closed client. We couldn't duplicate it on our systems.
I knew the configuration they had, so I wrote a couple of test doubles. One saying the client is closed, the other providing the customer's configuration.
I then wrote the test on the method that was timeing out. When I ran it, I got a time out.
I could then fix the code, make sure I got the same output as before, all without actually closing a client.
Final test: set config as customer, close client and run. Time out.
Import patch transport.
Run again - works fine.
Conceptually, it's about encapsulation and removing dependencies.
One reason why this is good, is where one class is dependent on another, if you change the depended class, then you risk breaking the depending class. In my first example above, if I change zdao I risk breaking zcontroller. I don't know where the issue lies - it could be in either, but my app doesn't work.
By having an interface, I decouple zdao from zcontroller. Now it has to go through zif_dao.
Let's say I make some changes to my application - both classes - and now it doesn't work. I run through my test classes on zcontroller and find that they work! I now know the problem is in zdao. If I'd not had an interface, it would take me longer to track the method down.
Another reason is design patterns. I use the state pattern to handle the differences between display mode and edit mode.
In my controller class, I just a reference to an interface for command handler. I send whatever commands I have to it, and it responds appropriately. I have one implementation of the interface for display mode and another for edit mode.
If I didn't, in my controller class I'd have multiple if display_mode then... else ... which is error prone. With my interface implementing classes, I don't have to worry - I know exactly which behaviour I'm working on.
TL:DR interfaces makes applications easier to modify and add functionality while being sure you're not breaking anything else.
And now I must find information about state patters... thanks mate (*said in a disgusted voice*)
It's one of the simplest.
You're welcome. Pal...
Nice, thank you for adding value to that topic 🙂 That would be nice to clearly see both that _utility is in the PRIVATE SECTION and how the local test class is made a local friend (you say it but maybe it's not obvious to people).
It was on my mind to do it, but my grandchildren turned up.
I've clarified it now.
"Now, we all know now that is a bad idea, but back then, we were young, naive and full of dreams."
I was laughing loud about it. Nicely formulated 🙂
I like this approach. Basically a wrapper around the static methods with an interface. Almost the same as we should deal with mocking standard FMs.
Inherently there is nothing wrong with static-methods as long as the output is defined by the Input.
The problem is with the static-variables: So don't use them. (It's the same for the function-modules: Input should define the output. So don't use global variables if you can avoid them (I'm looking at you screens).
The chances of errors doing it this way are smaller because one can not use or change the object-variables. Only the input-variables are available.
Generally one should try to create objects whose variables don't change after construction. As it is immutable, keeping track of the state of an object is indefinitely easier.
Static variables are only really useful in the context of singleton or multiton patterns.
I have used this technique, as well, although if the object I need to wrap is used frequently (e.g. a standard SAP function module or something), I have started using global interfaces/classes to wrap it so that I don't have to reinvent the wheel every time I want to create a test double for that same object.
One small thing I usually do differently is not instantiate the depended-on component in the class-constructor. When you do that, then even when the unit tests run, the productive version of that class gets instantiated, then overwritten when the test double is injected. Instead, if I'm using a local class to wrap the object, I will use lazy instantiation of the depended-on object. To use your example, it would look like this:
This has the benefit that if the test double is injected when the unit test is executed, the productive class is never instantiated. It also allows you change a static attribute to an instance attribute. 😉
If I created a global interface/class for my wrapper, then I will often use factory injection instead of lazy instantiation/backdoor injection.
Thanks for the blog! Always appreciate discussion about unit testing!
I guess I'm using a different form of lazy initialisation. I'm too lazy to write:
in each method needed access!
My approach would only cause issues if the constructor of the access class did some work. So, yes, it does affect robustness. I'll edit my blog.
Just wanted to add a few words:
I never delete these comments from the class-relevant local types 😎