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 😉