ABAP Unit Tests – Freedom!
In a very famous blog post from Nigel, Michelle Crapo and Jelena Perfiljeva challenged the defenders of ABAP Unit to provide some real world success stories. At first I was a bit skeptical since I’m not a big user of Unit Test in ABAP, but I do love a good challenge. After a few days here is my “success story”. What was the success I’m reporting?
Freedom from the shackles of the functional consultants! It’s liberating!
The first challenge was to find a process where the test are really useful. Testing the database or BAPI calls is hard, and ABAP code usually has a lot of them, so I had to find an example with a lot of in-memory business logic.
The winner? The monster that is SAP Convergent Invoicing, part of Hybris Billing (now C/4HANA something…. ). I’m working with postal services, and although we receive events from the tracking system for each object, we need to determine if these items (Consumption Items or CIT) can be grouped together using some very complex business rules.
So my development is organized in the following blocks:
- Service layer – Business logic (layer to be tested)
- Repository – DB and API operations
- Business Rules – Connection to BRF+ which contains all the business rules.
Although I’m a pretty experienced ABAPer, I’m not too knowledgeable of Convergent Invoicing technical details (I’ll avoid the CI abreviattion for clarity) and creating CITs is a bit tricky. While I do know BRF+ let’s pretend that I don’t, with Unit Tests I don’t need to. 🙂
So while I had the services layer pratically finished in terms of code:
- The repository layers was barebones (I didn’t know the APIs or exactly which tables to query);
- I had no real way to test my code as there were no items in the database
How do I test my code?
Unit tests to the rescue. While I don’t know the Convergent Invoicing table structure, I know how a CIT looks like in terms of business so I can mock the repository layer pretty easily. Just create some objects, and then mock the call, in this case to “get_groupable_cits”.
method set_repo_double. data: lo_new type ref to zclbil_cit. create object lo_new. lo_new->cit_data = VALUE #( zz_idevent = '1' zz_idproduto = 'EMF01' zz_idcaract = bs zz_opcaoserv = ' ' zz_idcontrato = '1' zz_coddest = '1500' ). append lo_new to me->test_data. create object lo_new. lo_new->cit_data = VALUE #( zz_idevent = '1' zz_idproduto = 'EMF01' zz_idcaract = 'CE001' zz_opcaoserv = '1600' zz_idcontrato = '1' zz_coddest = '1500' ). append lo_new to me->test_data. cl_abap_testdouble=>configure_call( repo_double )->returning( me->test_data ). repo_double->get_groupable_cits( ). endmethod.
Now I got some repeatable data to use for my tests without having to bother the functional consultants for more examples. I also mock the BRF+ layer just to make sure that everythings stays the same “forever”.
I don’t provide the complete example as you can check blogs ABAP Test Double Framework – An Introduction. They give a pretty good overview of how to use the Test Double Framework.
I can run tests without bothering anyone in a repeatable way, but the story didn’t end here. As the grouping logic is very complex, I forgot for example that CIT grouping also relied on the provider contract info, so I had to refactor a lot of code.
At the end of it? Just run the Unit Test to check that the refactoring didn’t break the rest of the code.
The ugly side of Unit Tests
Maintaining them. It’s hard enough when your business logic can change over time, but when you use something like BRF+ to make things dynamic, well it gets complicated. I’m mocking BRF so I’m focusing on present day rules, but end users can change the configuration by themselves, and it’s quite impossible to predict every permutation.
I also had to add test data to the dataset in order to test my new business rules so I ended up having to update all the tests that checked for number of groups, for specific keys, etc.
Some time was spent creating the test code, I would say half the time it took to write the production code, but half of that time helped me clear all the bugs in the production code so…. it was worth it. Still it does take time, and you need to invest.
Unit tests are very useful in providing a tool for developers to make the preliminary tests without help from functional consultants, and we know that sometimes that is used as an excuse for not testing the code. Functional teams still need to be involved, but the tools for repeatable and independent tests are there.
- I could run my tests of the services layers, without having a working repository which I assigned to another collegue.
- They force developers to produce more quality code, since I needed to better modularize my code for it to be testable. Separate classes with database dependencies, BRF+ rules, keep methods small, etc.
PS: I didn’t post a lot of code in this blog because that wasn’t my objective, there are already a lot of blogs that cover that. If you have some specific questions about how I structured the code, please ask in the comments.
PPS: I’m Hybris Billing stream leader and also functional lead. Nothing against functional consultants, just providing an example 😉
I try to learn the functional side too, so I can test my code. But, it is so very complicated now. I am the very first person to admit my FI/CO (BTC) is not the best. I can usually work my way through it. BUT right now I am learning so much on the coding front, it's starting to get really hard.
AND our business consultants are just that. Consultants that will be gone. SO I have to set up test data. Thinking ahead it would be a nice thing to be able to write the test code. Rarely is our DEV system refreshed. I can put something like Michelle's test touch if you dare on the master data. Then I would have it all the time.
Eventually I'll learn the system. It's all new (but really old too) for me. So this would be a great reason to add a unit test. (I'm already trying to do that - but it's hard right now)
Thank you for the mention - that meant I read this blog. And it's a very useful one.
It can be tricky to know the functional side. I'll admit that the problem with my example, as I revealed at the end, is that although I have a developer background and I try to keep updated, I'm actually a stream/functional lead, so I do have a lot of business knowledge.
It's not exactly a normal profile 🙂
Ah, um, not technical - huh? As Filpe has said - amazing you still took the time to do this. However, I would add that you know the technical side as well as the functional side.
Thank you for a very useful blog!
Another great post, well done! It's admirable how you still have the will to learn things by yourself and share your knowledge with the community even when it's far from being your primary focus. Most people wouldn't bother, but most people are not Joao Sousa.
Looking forward to your next post,
+1 What he said. 🙂
Thanks Filipe. Some people like puzzles, these are my own kind of puzzles.
Sharing encourages me to focus on a particular situation/use case, and that helps me learn because I can't just quit half way, it forces me to face the real challenges. The main reason I answered Q&A on SCN (not so much anymore), was that for the more interesting questions I had to go look for the answer myself.
I love a good technical challenges, too bad the culture in my country forces one to go into to management if he wants to earn more then average.
Thanks - so great to see excellent testing examples.
Thank you for sharing! The more examples the merrier. "Test double" was the most (or maybe the only 🙂 ) part of Unit Test that I could see a good use for. Was planning to go back and review that part of the openSAP course but, alas, never got around it. The limitations make me think though that eCATT or something would be a more effective tool for what I had in mind.
Sometimes no matter how awesome the tool itself is it's just better suited for certain things and not necessarily the others.
Never got around to use eCatt, shame on me, but it's more of a E2E test kind of tool. These unit tests are purely for code, and for small bits.
I think the main issue with unit tests in SAP is that most of the code written is opensql or calling BAPI. While opensql can be mocked by opensql test double, you're screwed on the BAPI side.
SAP CM(Convergent Mediation) has out-of-box connectors towards SAP CC supporting different types of charging requests against ChargeableItems, e.g. advice of charge, charge request, session based charge, deferred rating, notification from CC regarding customers credit updates, etc.
CM also supports RFC calls, e.g. BIT_CREATE_API for creating BITs, CIT_CREATE_API for creating CITs, etc.
Yes it does, but in what way does that relate to my story?