SAP Open Course: Unit Testing: Week 6: Working with Existing Code
This was the last week of a course about Unit Testing and Test Driven Development which has caused all-out war in the ABAP world as people fight to the death to defend or attack the concepts
This week the subject was EXISTING CODE and TEST DOUBLE FRAMEWORKS and THIS and THAT and THE OTHER and basically anything not yet covered.
Unit Tests on the Titanic
LEGACY CODE
As I keep stressing despite the fact that OO ABAP was introduced in the year 2000 with SAP 4.6C the take up has been shockingly low in the intervening period, as the recent online debates have shown a lot of people still program procedurally 100% of the time and have teams that “lack OO knowledge”. In fact one benefit of this course is to make the people who are taking it try and get their heads around OO concepts.
I have posted a vast number of blogs about why I think OO is good, based on a lot of real world experience, but it clearly is not everyone’s cup of tea.
In any event the end result of OO not really catching on in SAP world is that the vast bulk of existing code out there is procedural. Since on the internet and even in this course all examples given of how to introduce unit tests presume the program to be changed is already 100% OO and in real programs are 100% procedural this tends to make people think “this is nothing to do with me” like the Gas Board used to say on “That’s Life!”.
Nonetheless it is possible – I have done it – to use unit testing with procedural programs without changing them at all. The unit test classes have to be OO of course, and I am not saying it is easy, you need a LOT of work in the SETUP and TEARDOWN methods, but it can be done, and in my case I used this technique to transform a program which did not seem to be able to do anything right to one that performed flawlessly, despite the never ending functionality changes coming from the functional business owner.
Really though it is far less effort to start making the procedural program slightly OO than to spend ages using a complicated SETUP method to mess around with global variables and the like.
This involves extracting database calls and the like into methods of database access classes, or UI calls into UI classes and so on. There is no way you can do this to a huge procedural program all at once nor would you want to. This is where the ever popular “island of happiness’ comes in.
LEGACY TROUT
I have said this before and I am going to say it again word for word. I say when you have a change to a big program, write a test for the new behaviour, it fails, change the code, the test passes. Then you have one unit test. Let it go at that, do not bother refactoring anything else.
When the next change comes along to the program, the next week, to a totally different part of the program, repeat the procedure. Then you have two tests, and furthermore you know the second change has not broken the first. At this point you have 0.05% of the program under test.
If you rigidly enforce this then after a while you will benefit from what I call the law of “no-one is every satisfied” which translates to “if a part of a program is being used by a human, sooner rather than later they will want it changed”. Therefore after a five year period every single part of the program that is in use will have been subject to a change request, and thus will have a unit test of some sort. This is very simplistic, but that is the general idea. New programs should have tests from the start.
No One is Ever Satisfied
STOCK OPTIONS
One problem is that of the SELECT-OPTIONS and PARAMETERS in “type one” programs. These are global variables, which as we have seen are poison to unit testing. Since global variables are “seams” you have to have a special class to store the values. In real life you just stuff that class full of the values the user entered; in a test you stuff that class full of fake values.
I have been doing that for a while and wishing there was a better way, but there is not. If there is please tell me. One benefit the course instructors noted was that SELECT-OPTIONS have to be very short or you get a syntax error so you end up with values like S_DFRM whereas the target variable in the class can have a name up to 30 characters like DATE_FROM_RANGE or whatever.
What’s got a Hazelnut in Every Bite?
A complete change of topic! The title of this week’s course was “existing code” but in fact most of it was about various test double frameworks.
Up until now in the course we have been creating our test doubles (mocks, whatever you want to call them) manually, setting the interface as “partially implemented” and then manually coding the methods we are “redefining” with our fake data.
There is a different way to do this which arrived with ABAP 740 as I understand it. Actually there was an open source Z project called ZMOCKA which does the same thing on lower releases. SAP must have taken note of that and built their own version, which is in fact quite flattering for the author of the ZMOCKA project. In any event the original concept came from Java, this is the ABAP version.
What you do here is instead of coding a definition and implementation of your test double you create your test double by passing in the desired interface to CL_ABAP_TESTDOUBLE and backs comes an instance of the correct type to be injected into your code under test.
What comes next is slightly un-intuitive. Via a series of method calls you tell your test double what methods are going to be called, what input parameters are going to come in and what values to return when that happens.
In fact it seems you tell the double what result you want before actually telling it what method is going to be called. Even though I wrote about this two years ago in my book, still the whole thing seems very strange and I will have to do some more experiments before I can tell if this is better or worse than manually creating your test doubles. The claim is that the automated test double framework saves you time, and it may well, but possibly at the expense of clarity.
I could even imagine someone creating a framework of their own to wrap the test double framework to make it more understandable!
You can Ring my SQL
As of Release 7.51 there is a similar framework for creating “test doubles” of SQL statements. Up till now we have been wrapping SQL SELECT statements in database access classes which return hard coded data.
With the OSQL test double framework what you do is make a method call saying what database table is to be mocked and pass in an internal table of data of the same type as that table. Then by means of black magic (in fact the kernel) when the production code does the SELECT in fact the internal table is used as the data source rather than the database.
What this means is you do not have to remove the SEAM that is database access in the production code, so that is less work. In addition in effect your SQL statement is being tested, as if the internal table mirrors the actual database data and the wrong result comes back, that could indicate an error in the WHERE clause.
That is the positive aspect – on the other hand I rather liked the concept of isolating database access into its own class. I suppose you could still do that here. Also it could be argued that things that work “behind the scenes” make it less obvious what is going on a la logical database.
CDS – Miami
Enter Horatio.
Horatio: (puts sunglasses on) Baa Baa (takes sunglasses off) Black Sheep (puts sunglasses on) have you (takes sunglasses off) any (puts sunglasses on) WOOL?
Cue theme music.
The CDS test double framework works on exactly the same principle as the SQL test double framework. It is available as of ABAP 7.52. This time instead of replacing a SELECT from a database table you are replacing a SELECT form a CDS view. I don’t think there is anything else that needs to be said about that.
I will say that a lot of people taking the course must have been really puzzled as (talking about ERP systems only) I reckon maybe only 1% of people are on 7.52 (you need to be running S/4 HANA on premise) maybe another 2-3% are on 7.51 (again you need S/4 HANA on premise) and the other 96% are on lower levels. My company is in the midst of an upgrade from 7.02 to 7.50.
Yet in the course it was never mentioned that the SQL and CDS double framework was only available in higher levels. I can only presume that inside SAP as they get such things five to ten years earlier than your average customer they forget not everyone has them.
SEAMINGLY USELESS
Now we come to TEST-SEAMS which sadly are available at lower levels. I say sadly because I wish they were not available at all.
The idea here is you take your production code and you put a START-OF-TEST-SEAM / END-OF-TEST-SEAM statement around a database access or other sort of dependency. Then in the test code you do a TEST-INJECTION which replaces the code in the area of the test seam with some fake data.
On the positive side thus works. On the negative side you still have to change the production code, in my mind it is just as much of a change as replacing the seam with a call to an object method. I have been told that TEST-SEAMS are for when there is “no other possibility” but I cannot think of anything you cannot wrap in a method call. Someone give me just one example and then I will shut up about it.
Worse, now the production code knows it is being tested which is horrible. It is like saying IF TEST_FLAG = ‘X’ DO THIS ELSE DO THAT. In fact if you might as well do just that and not bother with the test seam statement. You get the exact same result.
The analogy is what brought down Volkswagen recently – they had code in their cars which said that if you are being tested produce really low amounts of pollution, if running productively (on the road) produce the normal high amounts of pollution. The unit tests passed, but in real life they got I into huge trouble. You could not do that if the production code was unaware if it was being tested or not. Maybe TEST-SEAMS were invented for Volkswagen.
Where be that Blackbird? I see he, and he CI
CI stands for “Continuous Integration”. This concept arose from languages like Java where everyone develops on their own machine and then “checks in” their code to the main repository at which point of course the new code might break all sorts of things. Thus you need an automated check very frequently to make sure the checked in code has not in fact stuffed things up.
In ABAP that whole concept makes not quite as much sense, as everything is all together in one repository so you cannot delete something that is still in use, for example. Nonetheless of course you can still change a class or a function which compiles fine on its own, but other code that depends on it suddenly breaks due to a new mandatory parameter or whatever.
There is a report (SAP term for program) called RS_AUCV_RUNNER which can be run on a regular basis and fire off all the unit tests and notify someone when any start breaking. This can be scheduled via the ABAP Test Cockpit (ATC).
Now if the unit tests just tested one class at a time that would not do you much good, as the fact the classes no longer play nicely together will not surface.
However if you have been writing tests using “Behaviour Driven Development” then likely all your tests involve more than one class, even if some of them are test doubles. These sort of unit tests are called “multi-level” tests by the course instructors, and “acceptance tests” by the wider testing community as in some automated test frameworks such as FITNESSE. The latter is in the form of a wiki where business people can add new tests and see if they work.
Oddly enough in my real life experiments thus far all my unit tests have ended up in this bucket, testing an expected program behaviour involving the interaction of several classes.
One point the course instructors made here was that if any of your tests have dependencies they are deemed “unstable” as the result varies at random, and this ruins the whole reputation of unit testing. This is more likely to happen in the early days, when that reputation is the most important, as if people get disillusioned in the first month, the whole thing is dead in the water.
Grandmaster Melly Mel: Guidelines for Writing New Code
The very last unit in this course focused on guidelines (I cannot say the term “best practice” without being physically sick) for writing new code.
Here are the notes I made. Everything that follows I agree with, as if they said something I did not agree with I would have either not written it down, or written it down coupled with a load of abuse.
- Do no work in constructors. They are vehicles for injection of dependencies or do something that can be mocked, but no complex business logic please, as that makes for untestable code. The irony is that most people when starting in OO do tons of stuff in the constructor. In my TDD experiments I found making the code testable involved taking more and more out of constructors until I pretty much did not need them anymore.
- For any class at all put all the public methods and attributes in an interface which the class then implements. This is because you always want to make your classes as reusable as possible so you never know in the future what other code will want to use your new class. That other code will want unit tests so it will want to be able to mock your new class i.e. create a test double, and you need an interface for that.
- This whole focus on interfaces makes me realise why you should not test private methods. They are the low level implementation details and may change all the time, whereas your public methods are “published” as it were and this means their nature changes much more rarely.
- The good news is that in Eclipse apparently you can create the definition of the class first and then extract all the public bits to an interface at the press of the button. Once again, that sounds too good to be true, I literally cannot wait till I start being able to use Eclipse at work which should be in just over a weeks’ time.
- As might be imagined the rule with variables is “be as local as possible”. I think that goes without saying – the more “global” a variable is the more scope for so called “side effects” where its value changes unexpectedly.
- This means you have a horrible, horrible trade off whereby – horrible choice number one – you can have all variables in a method local or parameters and have each method have a huge unusable signature (almost as bad as a BAPI) and moreover you get” tramp data” being passed into a method. Tramp Data is data which goes into a method for the sole purpose of being passed into another method the first method calls. I wrote a program like that once, but I would not do it again.
- The other horrible choice is to have most of the variables be “member variables” which are not quite global variables as they are only “global” within an instance of a class, but they can be changed by any method of that class so there might well possibly be “side effects”.
- So the course instructors recommended, and I wholeheartedly agree, and so does the guy who sits opposite me at work, and I did a straw poll of the bus queue and they all agreed as well, that when you have data which is “logically” global, then encapsulate it in a class with read only public attributes so the values are immutable once created. The obvious examples is customising data – there is no way that is going to change during the run of a program, and even if it did (transport going in) you would not want the start values to change for the current transaction. So we have a global customising object with all the immutable values, so that is out of control of the calling classes, so no sneaky method can go around changing them causing side effects
- Use SOLID principles. Say no more – if anyone thinks they are a load of old rubbish, not worth spitting on, then so be it. I will argue no more. By the way if anyone DOES think this can they indicate so in the comments? They would not be the first one from what I see on the internet but then people are humans and argue about everything, which is hopefully a positive thing, as opposed to blind faith.
Lastly the “Law of Demeter” of what is sometimes called a “Train Wreck”. What do these funny terms mean?
The example given was along the lines of MO_DOG->MO_MOUTH->MO_TONGUE->BARK( ).
This is called a “Train Wreck” because it looks like train carriages tied together with the “->” construct.
The problem is that the inner workings of the dog is exposed using this method. The correct way to do this is to have the BARK method as public in the interface and all the other methods private. Inside the dog you can bark any way you want, the caller does not need to know how this happens.
Perhaps you want a more sensible example?
How about MO_CONTROLLER->MO_SPECIFIC_VIEW->DISPLAY_WOTSIT_SCREEN( ).
I say you should change that to be MO_CONTROLLER->DISPLAY_WOTSIT_SCREEN( ) and within that method it might call MO_SPECIFIC_VIEW or it might not depending on how it is feeling, but however it decides to do it the WOTSIT_SCREEN is displayed. For example it could start off using CL_SALV_TABLE and then the developer swaps that for the CL_GUI_ALV_GRID when the users want something to be editable.
The point is that those big long list of method calls describe how something works in detail and in OO detail is the last thing we want, we want abstractions which is to say at the top level WHAT is happening as opposed to HOW it is happening. The “what” changes rarely but the “how” we often want to change all the time as we come up with ever better ways of doing the same thing.
For example to use current events you may have a program which analyses data for “exoplanets” to decide if it is enough like Earth that we can live there and stuff that planet right up as well. This is a big focus of scientists worldwide at the moment. In the next few years there will be a series of bigger and better telescopes being launched into space but the nature of the data will not change, there will just be a lot more and probably more accurate. If the algorithms in the analysing program are correct to start off with where does it care where the data is coming from?
So MO_PLANET_ANALYSER->MO_HUBBLE_TELESCOPE->SCAN( ) is BAD.
GOLF COURSE SUMMARY
Too Busy to Improve
The course is at an end now, so I would just like to give a general overview.
First of all I thought it was brilliant content wise. I learned a whole bunch of new stuff and I am supposed to be somewhat of an expert in this area already (one of the 1% who actually use unit tests) and I really hope a huge number of people at the very minimum grasped the vague idea of what is trying to be achieved by Unit Testing and TDD and indeed OO programming in general (and ABAP in Eclipse) (and the new ABAP constructs).
The actual “style” of the videos I may have not been 100% positive about. As always I recommend everybody to Toastmasters International when it comes to public speaking of any sort. As an aside Toastmasters count talking to a stranger in an elevator (lift) as public speaking as you have a short period to get your point across, even if your point is to explain who you are and what you do.
I talk in public all the time and Toastmasters has made me immeasurably better. The process of improving your public speaking is also a lot more scientific than you might think, which may appeal to some of the more technical types inside SAP.
In any event I am very glad indeed thus course happened. I have been saying more or less the same thing for many years to whoever will listen which is not very many people in ABAP world.
This may make the practice of TDD and Unit testing more mainstream or it may fall on “stony ground” once again. At the very minimum it has sparked a huge amount of debate on the SCN, a forum in dire need of such debates after it lost most of its members in recent times.
I am going to keep using TDD / ABAP Unit in real life, and will keep blogging about my experiences good or bad. I encourage anyone who also tries to use it in real life to do the same – if it all falls down then fair enough, tell us (the SCN) why.
If you hate the concept and see it as madness or worse I would also encourage you to try it anyway, and you will either be surprised, or more likely (due to the fact us humans make up our mind if something is good or bad before doing something and thereafter no evidence will sway us to the contrary) feel vindicated then tell us why it did not work as well. By the way I am 100% sure I am just as guilty of deciding this is good and subconsciously ignoring all contrary arguments though I do make a big effort to read them all.
I spoke about this (ABAP Unit / TDD) at the UK SAP User Group last week, in the next few months I have plans to speak about this at SAP Inside Track in Rome, and maybe at SIT in Hamburg, and to a company in Cologne, going on and on about this like a broken record until no one can take it anymore and either they start doing it or commit ritual suicide rather than hear me keep on about it, or I get assassinated. Maybe I could pass out red hats with the slogan “Let’s Make Testing Great Again”.
SUMMARY OF SUMMARY
From my point of view that LEGO picture above says it all. Management, and even developers, say that introducing crazy things like unit tests will slow them down and they are so busy that cannot happen.
The contrary argument is that the reason they are so busy in the first place is that they do not have unit tests and thus have to spend all their time fixing problems.
You either believe that or you do not – hopefully, in my organisation at least time will tell the tale. It will either be a gigantic success or colossal failure and I will let you know either way,
Cheersy Cheers
Paul
The fact that you have the time to 1) follow the course and 2) write about it already indicates that unit testing works for you, right? 😉
I was so excited when I heard about the SQL doubles. And so sad when i found that I don’t have them on my release yet.
I agree that the test seams keywords are an abomination before the Lord.
I love the Lego cartoon. I can think of some people who need this tattooing (mirrored of course) on their foreheads…
I have a red hat.
Concerning the Law of Demeter - do you think it means that if you're programming and you end up with dog->getHead( )->getMouth( )->getTongue( )->bark( ), that's a code smell that needs refactoring, or does it mean (for example), that in web dynpro, we should stick with the generated code, and not chain that together?
I.e. use
instead of
I.e. is chaining wrong when using others' code that you can't change?
In the WDA example below it does not matter if you do it all in one go or in two or three lines, as you still know the internal details i.e. that the context has nodes. It is not a trade secret.
In the barking dog example it is all about being able to change how the dog goes aboit barking, maybe out of its ears, without having to change the calling code. If the calling code knows the dog has a mouth then you are sunk.
Possibly a more ealistic exmaple if the good old SALV. You knows the SALV has a COLUMNS object and that this object in turn has various COLUMN objects within it, and so chainging evrything together seems the way to go. Why I wrap the whole thing in a Z interface is so I can swap between SALV or CL_GUI_ALV_GRID or even the REUSE_ALV_LIST function module if I so desire without changing the calling code.
With WDA by definition you are not going to be swapping the UI technology for something else so you can go gangbusters with the chains. That sounds a bit rude.
Thanks. I'd hate anyone to think that chaining is, automatically, a bad thing.
(not related to your question: hopefully, there is the WDA Unit Test Framework)
You mean like this?
Nice recap! I'm a lego person, I'm afraid. No time now. But there WILL be. I'll make sure of it.
Michelle
Thank you for saying that! When they started talking about it, my first thought was "how is this better?" and apparently I'm not crazy. 🙂
That Lego picture has been around for quite some time already (there are also numerous versions with the cavemen). First time you see it it's funny and thought-provoking. By the 3rd time it gets a bit annoying and you start questioning if it's actually an appropriate illustration.
Will TDD make our ABAP programs run any faster? Is it really a square wheel vs. round wheel or is it more like the other guy simply offering fancier rims? 🙂
In any case, I agree that one of the best outcomes of the course is all the SCN activity. Although I feel like I learned more from your blogs than from the course itself.
Thank you!
It was the first time I had seen it, so naturally I thought it was funny.
In any event, regardless of whether TDD works or not it's intention is not to improve the performance of programs (how could it?) but to force the design to be better (more generic / decoupled / maintaible and generally SOLID) thus square to round wheel, and to save time later on the maintance cycle when you change something and an unrelated part of the program breaks and you only discover that in production, and spend ages investigating. That has happened to me dozens of times.
So the idea is not that it speeds the program up, but that it speeds you up. As I said, you might not agree that TDD works, but that is the intention. In the same way the Titanic did not make it to New York, but was trying to get to New York, which is not an unreasonable aim, in and of itself.
So I think the Lego Men are bang on the money.
Cheersy Cheers
Paul
I'm not saying TDD / unit test does not work. I'm just questioning its value and ROI. Not every ABAPer has an environment where there are 100 developers working on a "product", which seems to be what the course presenters are dealing with.
Hi Paul,
Nice blog as usual. Re. refactoring you mention:
"The good news is that in Eclipse apparently you can create the definition of the class first and then extract all the public bits to an interface at the press of the button. Once again, that sounds too good to be true..."
This has been possible in SE24 for some time with the refactoring assistant. It's in the menu under Utilities > Refactoring > Refactoring assistant, there you can drag and drop components up and down the class hierarchy and from class to interfaces.
Once again, I learn more from the blogs this course generated than from the course itself. 🙂
After using Eclipse for a while some time ago I now prefer working in the Source Code-Based editor if I have to use SAP GUI.
Little fun fact: The Refactoring Assistant is only available in the Form-Based editor.
Thanks to your comment I found it again 🙂
One thing I have not seen mentioned in the course, is how the runtime environment determines whether test code is executable. For a simplistic example, the only advantage that I see in
is that it's enforced in the language syntax check, and does not rely on a variable being set by the tester (whether that IS an advantage is another argument).
However, what is the actual mechanism in the runtime environment that enforces this ?
Is it just the the settings settings in SCC4 and / or table T000 ?
Martin
Each TEST-SEAM block has a name (your example doesn't compile). Its inner code is replaced only if there's a corresponding TEST-INJECTION, which can only be defined inside a test class.
And a test class is compiled only if : CLASS - FOR TESTING "The source code in a test class is not part of the production code of the program and is not generated in production systems (controlled using the profile parameter abap/test_generation)"
Thanks, Sandra
That profile parameter is what I was after - Obviously it's turned on in the SAP Developer editions I've been playing with lately, but it may be worthwhile checking on any other system. According to, the "Production Code and Test Code" help page for nwpi711:
The default for customer systems if OFF.
whereas this is not explicitly mentioned in the latest (SPS22 at the time of writing) "Production and Test Code" help page.
hth
Yes, the TEST-INJECTION statement is the switch you're searching for.
Concerning the "test logic in production" test smell. It means that "The code that is put into production contains logic that should be exercised only during tests."
Here we have test logic in production. The do_something_else part was not designed to work in production but might run accidentally in production if we mess up with the test-flag. While we believe the test code should never be hit in production, we cannot be sure about that!
And here is the same example with a test-seam:
With test seams we don't have this risk. There is neither an IF-condition nor the test-friendly code inside the production code. The only thing is the trigger-point which enables the test code to exchange the test-unfriendly code during test execution.
The ABAP runtime takes care that the test code will never ever be called in production.
ABAP Test Seams help you to avoid test logic in production!
It simply adds a trigger point to the production code, no test logic, no condition evaluation, no flag.
The TEST-SEAM statement itself is not executable and in a real production environment it is not compile at all, just ignored by the compiler.
Hi Michael Gutfleisch,
thank you for this clarification. In real tests there may be different injections for the same seam. Implementing this with IF statements would be even more complex.
A test seam provides also the option to transfer values to the injected code via a local class for testing.
Thank you very much,
Rainer
Motivating your management to encourage TDD ? Then ask them how many times they (or the customers / business) ask How Long Will the Testing Take?
hth
Hi Paul,
in the last weeks of the curse I have been running late, mostly only scanning the slides and taking the assignments on the last possible day.
This week I missed taking the final exam by about 1 hour, so no record of achievement for me 🙁
I still think it was a good experience taking the course, and I very much liked the insights gained via those blogs by you and others!
Thanks a lot ya'll!
Some more thoughts on the guidelines for new code:
- When scanning the past weeks, I had the feeling, that if you have been following common (well, or not..) software patterns in the past, you already have some of the effects that are desired.
e.g. "Program to an interface" . I already mention this in a comment to your week 4-blog
You could say that those design patterns help you in the same way as TDD does: they encourage you to write better code.
- I can totally relate to those 2 horrible choices (and I remember - again, from school I think - the term "hidden parameter passing" for using member variables instead of parameters).
-> I do like the solution with the Data Provider Class! There's also a blog on, written by Jörg Krause : https://blogs.sap.com/2017/10/31/the-global-variable-dilemma/ .
- "Do no work in constructors" – this suggestion was actually new to me, I'll try to keep this in mind
In summary, as others have said, the course and even more the discussion it provoked have been a very god thing in my opinion!
best
Joachim
I think the unpopularity of TDD speaks loudly. Seems to be a field of dreams. Build it and they will come. I'm not a baseball fan. Not coming. Unless I get some free tickets to the suite which is catered with a nice spread of food and drink. 🙂