ABAP Unit – Fact or Fiction?
Unit Testing in ABAP
At TechEd 2017 in Las Vegas I have scheduled a networking session on the Tuesday from 5:00 to 5:30 on the show floor, at the Mentors Lounge. The timing reflects the fact that I don’t want to schedule anything just before the drinks at 6:00 and as a bonus, if no-one turns up, I will at least be in the correct area for the drinks. You have to think these things through.
In any event the networking session is all about unit testing in ABAP. TechEd is all about the latest and greatest cutting edge technologies but it occurs to me that it does not matter how shiny and new your toys are, if you have not yet got the basics right, then you are still pretty much doomed to failure.
A networking session is far more interactive than a straight lecture, and though I will have some slides about why I feel automated unit tests are important, and tips on what I have found out regarding the best way to do this (and convince your managers) I am also burning to know if anyone in the entire ABAP universe about from me even dreams about thinking about possibly creating some ABAP unit tests for real one day in the far future.
Do You Know What I Did Last Summer?
At work I have a once in a lifetime opportunity to convince my peers, both in Australia and at the head office in Germany, that unit testing is the bees knees, and can make your programs rock solid stable in the face of the sea of ever changing user requirements.
This knowledge is very well known outside of the ABAP universe, and has been for a very long time indeed. I am going to try my level best to convince everyone this is the way forward, based partially on the fact I have been doing this for real to a greater or lesser extent for five years now and found that it does everything for you that is written on the outside of the tin.
SAP Cider Press
By a stunning coincidence, SAP PRESS wanted me to write an introduction and conclusion to “wrap” an extract from my good old ABAP book. The whole chapter on unit testing is available as a reading sample on the internet anyway, but they felt it would be better if I added my ten cents worth.
I warned them that I am a bit of a maverick blogger, I am to blogging what a certain person who cannot be named is to tweeting i.e. totally out of control and liable to veer off on any tangent based on whatever is crossing my mind at any given instant, relevant or not.
As an example, today I learned that UK brewer Fuller Smith and Turner, based in Chiswick, London are replacing many different aged IT systems with a new ERP system that combines all that functionality into one. They have called this “Project Wyatt” based upon the famous US Marshall and Gunslinger “Wyatt ERP”.
Moving back to unit testing, today I gave a presentation on the subject, and mixed the more important “why” you should do unit testing with a touch of the actual mechanics of making your programs testable.
Here is a slide about mock objects:-
The slide above presupposes you have identified all “dependencies” in your code – in this example places where you do a database read, and then isolate all such reads into a database access class.
This is the “single responsibility” principle. That class would have one responsibility, namely interacting with the database. Now is the time to move to the extract from the book which talks about such “mock” objects.
Implementing Mock Objects
After you’ve isolated each dependency into its own class, you can change your existing programs to take advantage of the ABAP Unit framework. There are two steps to this:
- Create mock objects that appear to do the same thing as real objects dealing with database access and the like, but which are actually harmless duplicates solely for use in unit tests.
- Make sure that all the classes under tests (often a unit test will use several classes, but there is always one main one that you are testing—the class under test) are able to use these mock objects instead of the real objects, but only when a test is underway. This is known as injection.
Mock Objects vs. Stub Objects: When talking about mock objects, the terms stub and mock are often used interchangeably; technically, though, there is a difference. If you’re testing how your class affects an external system, then the fake external system is a mock, and if you’re testing how the fake external system affects your class, then the fake external system is a stub. (Either way, the point is that you use a fake external system for testing.)
Before jumping into creating mock objects and injection, let’s first take a quick look at test injection, introduced with test seams. This is not how you should implement mock objects, but you should see it in action at least once before dismissing it.
1 Test Injection for Test Seams
Test injection for test seams is the poor man’s version of implementing mock objects. Instead of replacing entire routines with a duplicate inside a mock class, you flag sections (one or more lines of code) inside of such routines so they can be replaced with different code during a test. In other words, you have the option to surround sections of production code with test seams. When the program runs in production, the actual code within the test seam is executed. When running a test, you define some bogus code that runs instead, the format of which is as shown below.
METHOD fire_nuclear_missile.”Test Method
* Set assorted variables, as if you had read them from the
* actual database
user_answer = ‘1’.
This works fine; a test injection can be empty and so no code is executed during the test, so no database data is read, no missile is fired, and all is well.
This is all well and good but don’t do it; it’s more trouble than it’s worth, and if proper programmers catch you, they’ll make you stand in the corner with a dunce cap on your head. Instead, proceed according to the next section.
2 Creating Mock Objects
For testing purposes, what you actually want is to define mock classes and mock objects. Mock classes are classes that run in the development environment. They don’t really try to read and write to the database, send emails, fire nuclear missiles, and so on, but they test the business logic nonetheless. Mock objects follow the same principles as regular objects; that is, in the same way that a monster object is an instance of the real monster class, a mock monster object is an instance of a mock monster class.
This is where the basic features of OO programming come into play: subclasses and interfaces. To continue the previous example (about firing nuclear missiles), you’ll next create a subclass of the database access class that doesn’t actually read the database but instead redefines the database access methods to return hardcoded values based upon the values passed in. Below you’ll see some possible redefined implementations of methods in mock subclasses that could replace the real classes in the example.
METHOD read_customising. “mock database implementation
* IMPORTING input_value
* EXPORTING export_vlaue
export_value = something.
export_value = something_else.
export_value = something_else_again.
ENDMETHOD. “read customising mock database implementation
METHOD popup_to_confirm. “mock user interface implementation
* RETURNING rd_answer TYPE char01
rd_answer = ‘1’.”Yes
ENDMETHOD. “mock user interface implementation
METHOD fire_missile. “Mock External Interface Implementation
* Don’t do ANYTHING – it’s just a test
ENDMETHOD. “Fire Missile – Mock Ext Interface – Implementation
In this example, you create subclasses of your database access class, your user interface class, and your external system interface class. Then, you redefine the methods in the subclasses such that they either do nothing at all or return some hard-coded values.
Object-Oriented Recommendation: In order to follow one of the core OO recommendations—to favor composition over inheritance—you should create an interface that’s used by your real database access class and also have the mock class be a subclass that implements that interface. In the latter case, before ABAP 7.4, you’d have to create blank implementations for the methods you are not going to use, and that could be viewed as extra effort. Nevertheless, interfaces are a really Good Thing and actually save you effort in the long run. Once you read books like Head First Design Patterns, you’ll wonder how you ever lived without building up class definitions using interfaces.
3 Proper Injection
Usually, classes in your program make use of smaller classes that perform specialized functions. The normal way to set this up is to have those helper classes as private instance variables of the main class, as seen here:
CLASS lcl_monster_simulator DEFINITION.
“Helper class for database access
mo_pers_layer TYPE REF TO zcl_monster_pers_layer,
“Helper class for logging
mo_logger TYPE REF TO zcl_logger.
These variables are then set up during construction of the object instance—see below.
CREATE OBJECT mo_logger.
CREATE OBJECT mo_pers_layer
io_logger = mo_logger ” Logging Class
id_valid_on = sy-datum. ” Validaty Date
However, as you can see, this design does not include any mock objects, which means that you have no chance to run unit tests against the class. To solve this problem, you need a way to get the mock objects you created earlier inside the class under test. The best time to do this is when an instance of the class under test is being created.
When creating an instance of the class under test, you use a technique called constructor injection to make the code use the mock objects so that it behaves differently than it would when running productively. With this technique, you still have private instance variables of the database access classes (for example), but now you make these into optional import parameters of the constructor. The constructor definition and implementation now looks like this code:
METHODS: constructor IMPORTING
io_pers_layer TYPE REF TO zcl_monster_pers_layer OPTIONAL
io_logger TYPE REF TO zcl_logger OPTIONAL.
IF io_logger IS SUPPLIED.
mo_logger = io_logger.
CREATE OBJECT mo_logger.
IF io_pers_layer IS SUPPLIED.
mo_pers_layer = io_pers_layer.
CREATE OBJECT mo_pers_layer
io_logger = mo_logger ” Logging Class
id_valid_on = sy-datum. ” Validity Date
The whole idea here is that the constructor has optional parameters for the various classes. The main class needs these parameters in order to read the database, write to a log, or communicate with any other external party that’s needed. When running a unit test, you pass in (inject) mock objects into the constructor that simply pass back hard-coded values of some sort or don’t do anything at all. (In the real production code, you don’t pass anything into the optional parameters of the constructor, so the real objects that do real work are created.)
Arguments Against Constructor Injection: Some people have complained that the whole idea of constructor injection is horrible, because a program can pass in other database access subclasses when executing the code for real outside of the testing framework. However, I disagree with that argument, because constructor injection can give you benefits outside of unit testing.
As an example, consider a case in which a program usually reads the entire monster making configuration from the database—unless you’re performing unit testing, when you pass in a fake object that gives hard-coded values. Now, say a requirement comes in that the users want to change some of the configuration values on screen and run a what-if analysis before saving the changes. One way to do that is to have a subclass that uses the internal tables in memory as opposed to the ones in the database, and you pass that class into the constructor when running your simulator program in what-if mode.
At this point, you now have mock objects and a way to pass them into your program.
The Monster Club
Children’s writer R.Chetwynd Haynes in his book “The Monster Club” defined Mocks as monsters that had been created due to protracted inbreeding between vampires, werewolves and ghouls. If they were to then breed once again the result would be a Shadmock, the most fearsome of all monsters who “only whistle” and you had better not be around when they do.
Just to be clear these are not the sort of Mocks I am talking about in this article. Here we are talking about dummy subclasses of the specialized classes in your program which deal with “dependencies” such as interacting with the database or an external system.
As a by-product of having to create such classes in order to make the program testable, you also end up with a better design overall, better in the sense you a swap out database tables, or even an entire external system, and the calling program does not need to be changed in any way – the good old “open closed” principle.
I would just like to end with a request that if you are going to Las Vegas for TechEd this year and your company either (a) already uses ABAP Unit or (b) spends an inordinate amount of time fixing production problems and you don’t know why, then pop along to my networking session in the Mentors Lounge on the show floor from 5:00 to 5:30 on the Tuesday 26th. It will be finished with a nice half-hour buffer before the drinks begin, as two years ago some naughty stalls started serving the drinks early.
Naturally I do not approve of such things, but just in case it happens again, that would be a good place to be.
PS At the SAUG (Australian SAP User Group) conference last week I learned Australian Energy Company AGL were using ABAP Unit. That is wonderful, I thought it was just me.
In the last few weeks AGL has been in the news lately because they announced they were replacing all their coal fired power stations with renewable energy power stations. They were called hypocrites for saying this on the somewhat dubious (to me) grounds that they currently have coal fired power stations and make money from them. Then they got attacked for taking jobs away from power station workers at the coal fired power stations, and indeed the poor old coal miners. You really cannot win.
In any event they use ABAP Unit, and so do I. What I would like to know, right here and now, is does your company use ABAP Unit? If so how did you manage to convince the powers that be?
This is the type of thing I want to talk about at the networking session in Las Vegas but if you use ABAP Unit, please add a comment to this blog.