ABAP TDD & Unit Testing: Trial and Error
Like many fellow SAP developers, I have taken the openSAP course Writing Testable Code for ABAP by Jurgen Heymann and Thomas Hammer. If you haven’t taken it, I highly recommend it. I have been wanting to get my head around these concepts, but didn’t really have the first notion of how to go about it let alone properly code for it. This course helped a great deal and provided some much-needed background on the concepts of Test Driven Development (TDD) and unit testing. Additionally, I follow the posts of the many other members such as Paul Hardy and Matthew Billingham, who also promote the concepts.
I recently had the opportunity to apply this practice on a piece of work enhancing inbound IDOC processing. When certain conditions are met, an additional database record must be created in a table separate from what is normally used. My approach was to use the “Island of Happiness” as described in week 3 of the course. With this approach in mind, I set about creating a new global class to perform the required task.
I should point out that I am still learning the processes of this new paradigm (to me at least) and it may appear that I am thrashing around at this, but this is simply how I personally learn best. Trial and error.
Without thinking, I created my new class and plopped a couple of new static methods, at which point, I stopped, realizing I should be using an interface. Since I code in Eclipse, I simply added the INTERFACES code line in my public section and used Eclipse’s cool “Quick Assist” link to generate the baseline interface. From this I simply used another quick assist to pull what methods I had already created up into the interface.
Great, interface created, on to implementing my methods. But wait, I am trying to use TDD. This is the thing for me, part of this new paradigm is remembering to perform the steps in the order prescribed by the course. So, I shifted my focus to the unit test class and established the basic framework. I added what I thought were the relevant tests for my new methods, which passed on the first try. Hmm… but it still did not seem correct.
Realizing my first couple of static methods rely on database reads, I had to think about that for a bit. This forced me to stop and rethink my approach to these initial methods. In very general terms…
- I needed an interface/class to handle the database read.
- I created the database interface and related class to handle database access (DAO).
- How to do I use this in my new IDOC class?
- Go back to the class static method implementation and convert the database access to use the DAO.
- Go back to my original interface add an injector method to inject the DAO.
- Implement the injector method.
- Back to the unit class, implement my DAO interface/stub class.
- Inject it into the instantiated IDOC class object.
I went back and forth like this for two or three other instance methods which required database access. In the end I lost track of the TDD process and focused more on getting the code under test (CUT) and the test correct.
Additionally, there could be some debate as to how to “inject” the DAO object, via the constructor or via a special injector method, or other means. I chose the injector method, simply because it made more sense for me at this point in the process. I am certainly open to discussion on this point.
Separately, as I created my test cases/methods, I discovered that based on the way I was coding the CUT and my unit tests, I would get false positives, sometimes based on incorrect interpretations of what I was supposed to get as a result and sometimes which unit test assertion I chose to use. As a result, I spent quite a lot of time going back and forth between the CUT and the tests and fine tuning both.
At the end of the day, it probably took triple the amount of time it would normally take for me to complete this, relatively small piece of code. However, the final product, in my opinion, was far superior to what I would have created.
Over time, with enough opportunity and practice, I think I can become much more proficient at TDD and creating unit tests, resulting in more robust code. The most important takeaway, for me, is to plan out the approaches/processes more carefully, so to know where and what will be need for interfaces/classes to handle processes outside of what the CUT is supposed to do; i.e. database access, function calls, etc.
If I can master this methodology, It’ll be all the better, as SAP and ABAP development move further into the 21st century.
Great blog! I'm also still learning how to do TDD and get stuff into test harnesses etc. And it does seem to be taking a long time! With the project I refer to in my blog it's hard to stick to TDD purely. I keep writing methods first! But then I catch myself and nip off and write a few unit tests and keep iterating until they're green.
I agree that the process guides the development and you end up with a better structured development.
I prefer to use FRIENDS for injection.
Thank you Matthew. The only way to get better at it is keep doing it, which I will do. It just takes a while to creat new habits, etc.
I only started doing this for real after the Open SAP Course. It is agony at the start, the first test took two days to write. However I am getting better at this each week - as long as I keep doing this.
It is so easy to "fall off the wagon" and go back to doing the deisgn first. However when the time comes to write the tests after the event they force you to reliase your design is wrong and needs to be re-done. After a while the light bulb goes on it would be faster to do tests first and have the design right to start with,
In fact the biggest surprise is that I find that I should not think too far ahead - I am used to coming up with the big picture first in my head and then writing code with that big picture in mind.
With TDD you just go through the requirements one by one, not thinking of the big picture, and the best possible framework starts to form itself, and it is always different from what you would have thought was the best way to structure the application before you started.
This is why traditional ABAPers think the whole thing is madness.
I wll re-iterate a point - when I wrote about testing PID retreival and other things, Jelena sais this was a collosal waste of time. I have come to the conclusion that writing tests for things that cannot possibly go wrong - like getting a PID - actually force me to write the program in a more SOLID fashion, even if the test itself is pointless/useless.
I think it is like in "The Karate Kid" when the master makes the student take a coat off and put it on again about a million times. That act itself was meaningless but taught the student something far more important (cannot rememeber what).
It does take longer - though it is getting faster with practice - but even if it took ten times a long, it would be worth it over the life cycle of the custom application due to it breaking far less often (or never) in unexpected ways, not having to spend hours debugging, and not lying awake at night panicking every time a new version goes into production.
SAP proudly say that using solution manager (or something) you can have two releases to production a year! Well done!
Graham Robinson on the other hand asks why we do not deploy to production several times a day?
Possibly because we are too scared. TDD and automated regression testing is one of the ways to slve this problem. The branching thing to roll back changes in a heartbeat is another.
Amazon deploys something to production every three seconds. Netflix claims a two minute period from an issue being logged to the fix being in production. These sorts of things would be one line codes fixes/changes no doubt or maybe not - if your automated regression testing is good enough it takes the fear away.
So in ABAP world we are taking 3 to 6 months to get a fix to production. To coin a phrase "all the other porgrammers" are taking seconds or minutes.
Should we be trying to improve things and aim for that increased speed? Or should we not bother because all those other programmers are "jumping off a bridge?"
When I was 14 (1982) the perceived wisdom in computer world was that text compression of more than 50% was impossible. That did not stop me at the tme trying to write algorithims (in 1K of memory on a ZX81) to try and beat that target. Did I suceed or not? I cannot remember and it does ot matter - I was TRYING.
So - is trying to release transports into production with zero risk several times a day possible? Some would say, no, nay, never, no, never, no more.
Humans going to mars is impossible as well. Having an electric car which goes further than an ICE car on one charge is currently impossible as well. There are many incurable diseases also.
Does that stop people trying to work on the problem?
That was a lovely rant. I will end by saying I like the "dependency lookup" with the factory and injector class. At work I created both for our current project the very next day after I learned the concept on the Open SAP Course.
Thank you for your response Paul.
I especially like your analogy to "The Karate Kid".
Additionally, I agree, with you, it would be very easy to fall off the wagon, with conscious effort to continue with the TDD practices.
I see your point as well, that perhaps not looking too far ahead would be best, when developing the code. It would probably be too easy to slip back in to old habits.
Every morning when I come into work I get an email telling me if any of my unit tests are failing due to changes I made yesterday.
I had a failure today and that was because I had copied over some logic from a big monolithic program into an OO equivalent, and the FORM routine which was now a method was doing two disparate things. It purported to be "verifying" the value of a field, but in actual fact in some circumstances it was changing the value it was supposed to be checking.
That is a clear violation of the SRP and the only way I could get the test to pass was to split the derivation and validation code into two separate methods, which is what I should have done in the first place, even if they lived together in the old program.
Thus having unit tests literally forces me to do the right thing and follow the SOLID principles.