Open SAP Course: Unit Testing: Week Four: Test Isolation
At this stage people were always going to either love or hate the idea of unit testing. As the course progresses the subject matter gets more complicated and thus more and more divisive.
This week’s subject was “Test Isolation”.
As a spoiler I am one of the ones who loves this more and more as it goes along as opposed to people who feel increasingly lost.
In any event there are two strands to test isolation – test doubles and dependency injection.
I have written about both subjects in blogs on the SCN for many years, and unit testing is even the subject of the free sample chapter in my SAP book.
Mines a Double
A test double is a class which pretends to be something it is not, but nothing looking at it from the outside can tell.
The very good analogy made in the course was that of a stunt double or body double. To the audience watching the film it looks and behaves just like one of the main actors, but it is not.
Why would you want such an impostor inhabiting your programs? Is this some of sinister “Invasion of the Bodysnatchers” situation where aliens impersonate parts of your program in a bid to take over the world?
Nothing is as it Seams
The reason is that traditionally code is full of “seams” which are rips in the fabric of space time through which your program interacts with other parts of the program or the outside world, in a manner different from passing in and out parameters.
There were a great list of possible seams, which include, but are not limited to:-
- Reading and changing the values of global variables
- Reading or writing to the database (described as the ultimate global variable, I have never looked at it this way before, but this is an accurate description)
- Getting the current time
- Printing Something
- Interface to an external system (e.g. proxy call)
- Confirmation Prompt to an actual human user
- Creating an Object
Why are “seams” bad? They are unpredictable, your routine is supposed to be doing one thing, and that one thing is what you want to test, but each seam can alter the behaviour of your routine “seemingly” at random making an automated test impossible.
You want to replace each such unpredictable same with an identical duplicate who looks the same and appears to act the same, but in reality always does the exact same thing every time. Put another way you do not want the code being tested to know whether it is running for real, or running as an automated test.
The result of this is you can run an automated test on a routine and all that is being tested is the business logic in the test, and not the “seams” or dependencies as they are usually known, on the ground that 99% of code “depends” on such things as reading the database.
In one of my programs I have bucket loads of conditional logic and mathematical formulas manipulating values to get an end result (quantity of an ingredient in concrete). However the starting values comes from a bunch of configuration tables, which in real life change all the time, as per business requirements, so essentially at random, and at random intervals. The temperature also plays a part. I want to test that the formulas work, I do not care about the actual values in the configuration tables. When I change the logic in the formula I want to test the conditional logic code that I changed, I do not want changes in the configuration data (or the current temperature) influencing the result one way or the other. If the current temperature influenced the test result you might get a different result every hour.
This body double “seam” business is, I admit, one gigantically strange concept to get your head around.
Interfaces in the Crowd
In the course matters now got very chaotic indeed. I could guess where they were heading but the presenters took the approach of having one “unit” about how to change your code so that it was “testable” and then another unit about how to actually inject the test doubles into the real code.
The problem was, so as to not get ahead of themselves they did not mention “injection” at all in the first unit, and so it appeared to be a series of examples of moving code around at random for no purpose whatsoever, as the end result was always the same after each change.
In particular the code examples kept getting “interfaces” added to them for seemingly no reason.
An “interface” is a very difficult concept to grasp at the best of times. I always use the Japanese analogy – they say a person has three faces: the face they show to the world, the face they shows to friends and family, and the face they only show to themselves (or nobody).
In OO world this has a perfect match with public, protected and private methods and attributes.
Also in both cases sometimes even more restriction is needed and the face you want to show to the world (public aspect) varies depending on what part of the world you are showing it to e.g. I am an accountant, I am a volunteer firefighter, I sometimes turn into the superhero known as “The Brown Bottle”. In each case you only want to show one aspect of yourself at a time.
An interface is like a mask a class can wear. In the above example a person class would implement all three interfaces and there would be three totally separate calling programs:-
- The calculate numbers class importing objects typed to the accountant interface
- The fight fires class importing objects typed to the firefighter interface
- The battle scary monsters class importing objects typed to the superhero interface
Each calling program could only access the part of the imported “person” that had been declared in the interface, e.g. the accounting program could not call the “fly” method of the person, only the monster battling program could do that.
That is a generally useful concept in OO programming – interfaces are often described as the “Fifth Beatle” of OO programming after Encapsulation, Abstraction, Inheritance and Polymorphism.
When it comes to testing it transpires that if a class describes its behaviour via an interface then it becomes ten times easier to replace with a test double. This is however, not obvious in the slightest.
So in the course we got the recommendation to create interfaces for every single one of your classes – which I agree with by the way – but without a coherent explanation of why you would want to do that in a way most people could understand.
Injecting a Little Humour into the Situation
The next module was all about “injection” and it started with more jokes and laughter a la:-
Deadpan One: Things are getting serious!
Deadpan Two: Ha ha ha!
Deadpan One: Ho ho ho!
As mentioned earlier injection is the process of creating test doubles for real functions like database access and sneaking them into the code under test, without the code under test knowing that the database access object (or whatever) has been replaced with an impostor.
This was the point when I thought the course got really good, the best so far, as it started teaching me things I did not already know, and making really good suggestions I had never considered. I have been reading and writing about this subject for years, but you never learn so much you cannot learn some more.
However to someone who was not already a so called “expert” on the subject this might have been the exact time they wanted to throw themselves out of the window.
Suffice it to say there are various ways to “inject” the test doubles into the production code. I had always been using so called “constructor injection” but the presenters outlined several different methods with the pros and cons of each one.
One that was new to me was “Backdoor Injection” which sounds a bit painful, but what it means is that the test class can be “friends” with the class under test and thus replace its friend’s private bits with identical looking fakes. I am not sure that is what friends should do to each other, but there you go.
I like their naming convention as well – the very next day on the program I am working on I renamed my test doubles from LCL_ (for local class) to LTD_ (for local test double).
Clearly in order to inject these test doubles (this is rather like the seventies film “Fantastic Voyage” where a miniature submarine is injected into a very ill man so it can impersonate his antibodies and cure the disease) you have to create them first.
I had always been creating a subclass of the object I wanted to fake, and redefining the methods to return hardcoded values or do nothing.
The presenters instead recommended the fake object be defined as not inheriting from anything but instead implementing an interface.
The difference is, when the real object is changed such that a new method is added, with inheritance the fake object inherits the new method and that can affect existing tests.
With an interface if a new method is added, then the test will either dump because the test double does not implement a version of that new method, or possibly be unchanged because the new method will be empty in the test double. I am not 100% sure about these “partially implemented interfaces” yet. In any case, with a dump, you are instantly aware of such changes, which is not the case with inheritance.
Since I have had that exact problem in real life using inherited test doubles, I consider their argument sound, based on actual experience, and I am convinced, and will change my evil ways henceforth.
Another idea I had not considered was to flag the definition of the test doubles with FOR TESTING so they cannot possibly be used in production.
Silly Terms of Endearment
I was fairly sad to see a lengthy discussion in the course of the various sorts of test doubles you could have e.g. spies, stubs, mocks, shadmocks, maddies, ghouls, humgoos and so on.
I wrote a little box in my book pointing out the difference between mocks and stubs and concluding I do not really care, and I suspect nor does anyone else. I have always called test doubles mocks in the past no matter what they do as in “mock database access” but in the future I might not even bother with that – test double is a good enough term on its own.
As has been pointed out the presenters were not making up those terms (spies and so forth) they were just quoting from a textbook.
There are tons of silly terms in computer programming world – tools called “Jenkins” and “GIT” and “ANT” and languages with stupid names like Franz Lisp, Groovy, Ruby on Rails and Avengers Assembla, and to the outside world such terms just seem plain stupid. The same with the word “ABAP”. Once these terms have been coined you cannot get rid of them, you just have to live with them, no matter how idiotic they sound.
As an example a blog on SCN yesterday was called “Using Camel’s Simple in CPI Groovy scripts” – now what would that mean to a causal observer?
Dissolving in a Solution
The rest of this week’s course was devoted to an exercise with a big video at the start explaining one day to complete one of the four exercises in detail.
Though I was very impressed with the examples there is one gripe that I have made before and will make again.
In every example – in this course and on the internet generally about adding tests – it presumes that the “legacy code” is object orientated. This is just not realistic, in ABAP world at least.
In ABAP world all your legacy code will be procedural. Thus you can see how confused people get who have only ever written procedural programs when they see OO programs and are instantly told how badly written these OO programs are and have to be totally redesigned.
Week Four: Conclusion
I have to admit I walked away from this week’s videos thinking “that was great” and it was the best so far. However any points that it scored with me, were probably lost on a large number of people who just found it really confusing.
I like TDD Traffic Lights – but only when they are Red or Green
One final thing – as promised I have been doing actually TDD at work for the first time ever this week. To say it has been an illuminating experience is a massive understatement.
I have been documenting myself as I go, and there will be a blog on this in the very near future, probably even this weekend, after all it is already three quarters written.
I am not 100% sure about these “partially implemented interfaces” yet. In any case, with a dump, you are instantly aware of such changes, which is not the case with inheritance.
As I understand it. They’re BRILLIANT. If you try to call a not implemented method of the interface, you get a dump. But PARTIALLY IMPLEMENTED only applies to test classes. So no-one else is inconvenienced.
I seem to have learned the same as you. Interfaces have no side-effects. Subclasses may. Use interfaces.
I had a sneak at 7.52(?) where all these techniques seem to be part of the language.https://help.sap.com/doc/abapdocu_752_index_htm/7.52/en-US/abentest_seams.htm
Now – can you go over to Bärbel? I think she’s getting slightly discombobulated. https://blogs.sap.com/2018/04/05/opensap-abap-unit-testing-course-getting-lost-in-the-weeds/
NIce to see that you're a member of the Volunteer Fire Department. The world is quiet here.
There is something in the higher versions of ABAP called TEST-SEAMS where you put the statement BEGIN-OF-TEST-SEAM and END-OF_TEST-SEAM in your production code around the area you want to replace at testing time.
I think they are an abomination and in my book I literally beg people not to use them.
The reason being it violates the principle that the production code does not know it is being tested.
As far as I can see the TEST-SEAM thing in ABAP is pretty much the same as writing IF TEST_FLAG = X DO THIS ELSE DO THAT plus it all works by black magic.
I was very glad the comedy duo did not mention them in their course. Injection of test doubles is the way to go.
we will mention TEST-SEAM in week 5, but we will not recommend them for the same reason you don't like them. But there are situations where they are the only thing you can use. Then you should know them.
I’m too lazy to search right now, but I vaguely remember reading somewhere on a SAP blog or in the docu that test seams are meant for cases where proper abstraction is too difficult, e.g. to facilitate adding tests to legacy code without refactoring.
BTW I’m with Matthew, PARTIALLY IMPLEMENTED rocks!
Thanks for another neat and illuminating blog post, Paul!
It put this unit testing stuff in general and this week’s material in particular in perspective and helped me to understand at least the gist of it a lot better. This is more than enough for my current purposes where "simply" understanding it is much more important than actually using it - much like the difference between a passive and an active vocabulary in a non-native language.
My 2 cents ...
# Another suggestion i liked was - "If the depended-on-component(DOC) doesn't have an interface, then create one in order to facilitate creation of test double(TD)".
I am slowly but steadily getting into this habit of creating "local" interfaces for DOCs like database accesses, function calls etc. Helps me to create TDs easily without much hassle.
# Keep the TDs as simple as possible. For e.g., no fancy setter-getter methods
Also AFAIK, if you add the addition FOR TESTING the relevant lines of code wouldn't be considered in the code coverage 🙂
On interfaces, I somehow remember (from school, I think) a pattern "always program against an interface" (as opposed to against the class itself).
I think the reason was: the class can change, as long as the interface is stable, things are fine.
[...looking it up on the inter-web, it seems to be a GoF-Pattern and the correct phrasing is: "Program to an interface, not an implementation." -> here's something to read up on it: https://www.artima.com/lejava/articles/designprinciples.html (from 2005 ! Also nice how Erich Gamma notes how he hopes that Eclipse will still be around in ten (-> 2015 – check!) or twenty (-> 2025 – we’ll see) years. ]
And I also remember when learning Java with Eclipse, there was a feature "extract interface" to support that somehow.
In general, I think this whole “UnitTesting in ABAB”-hype right now, it might be a good opportunity to go back to the old, old textbooks and read up on OO-Design-Principles - I guess they are still valid!
Our teacher always praised the Book "Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development" by Caig Larman (from 2004).
Seems you even can still buy it: (its ~ 90 €, tough!)
I know I owned it once, not sure if I still do! (It might have been eliminated with other "old stuff from school I'll never need again!).