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.