My experience with real world unit testing (ABAP Unit)
Unit Testing, ABAPUnit and test driven Development (TDD) have been recent hot topics here on SAP Community.
After having taking some parts of the OpenSap Course “Writing Testable Code with ABAP” a few months ago, I recently had an opportunity to try some of it out.
My situation: I had a classic ABAP-Report: Select some stuff according to selection screen, do some calculation or transformation (in my case: sum some things up, using collect), and finally do something with the result (in my case: create an IDOC).
I think I event created it using my template for those cases.
I had written this report with 0 UnitTests. And I probably would have head a hard time with the question “what and how exactly should I unit-Test here?!” anyway.
I had to look at the report again, when I got the following additional requirement:
An additional mandatory parameter “company code” should be introduced.
Also, the plants, which have been parts of the selection screen already, should be verified against that company code:
Only plants that are part of this company code should be accepted (otherwise an error should be given).
-> This is, what trigger “This are Unit-test-cases right here!” immediately.
The TDD paradigm “first thing you do: write a test” doesn’t work, as I need a least an empty structure (e.g. a class) I can test against.
[Only now, when writing this down, I realize, that I could have started with writing the test, and the create everything I use in it (classes, methods) with forward navigation. I think this was also advised in the course. ]
First thing I did (after adding parameter pa_bukrs to the selection screen) is write a class lcl_input_verification with an empty method verify_werks_against_bukrs.
(the existing coding is in lcl_report – my idea is that I probably don’t have to touch that at all!)
I then went for writing a test.
First thing I noticed: there’s no Test-Classes tab, as there was in the course – that’s because I don’t have a global class (“SE24”), but local classes (within my ABAP-Report).
But writing the test-class just underneath the other clases is ok and works fine.
It was realy helpfull that I had still access to the examples from the course, to look up things like
DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT."
"given "when "then
I created the test-class ltc_report_name with the first test-method: one_werks_belongs_to_bukrs.
As we did in the course, I wanted to start with the very verbose writing, using the given/when/then structure (beeing lazy, I create an AdT-Template for it :-))
I filled it up as follows:
"given data(cut) = new lcl_input_verification( ). "when data(result) = cut->verify_werks_bukrs( iv_bukrs = '0001' it_werks = value lcl_input_verification=>ty_werks_list( ( werks = 'W01' ) ) ). "then cl_abap_unit_assert=>assert_true( result ) .
As you see, I didn’t use a “SQL Test Double Framework” or simmilar, I just looked at V_T001K_ASSIGN in the development system, to get a valid combination.
I do know that:
– This test might break on other systems
– When customizing changes, this breaks my test.
However, and that was a recurring insigt: it was a solution available to me right at this time, and it helpt me to complete this one unit test (rather the getting lost in trying to lern yet another new thing™).
And: it doesn’t prevent me from changing (improfing, refactoring) it in the future at all.
So I had my test, ran it, watched it fail.
As right out of the text book, next thing I did was: make it pass, and use the simplest way possible:
METHOD verify_werks_bukrs. r_it_is_ok = abap true. ENDMETHOD.
Next thing is one of those:
- write the next test
- refactor production code
- refactor test code).
I went for refectoring test code:
– Move the ‘ “given ‘ part (create object and assing its refferece to the CodeUnderTest-Varaiable (cut)) to the setup method, creating member m_cut in this process.
– Brought the rest of the test in a more ‘compact’ form:
METHOD setup. "given m_cut = new lcl_input_verification( ). ENDMETHOD. METHOD one_werks_belongs_to_bukrs. "then "when cl_abap_unit_assert=>assert_true( M_cut->VERIFY_WERKS_BUKRS( iv_bukrs = '0001' it_werks = value lcl_input_verification=>ty_werks_list( ( werks = 'W01' ) ) ) ). ENDMETHOD.
– I also renamed the test class to ltc_input_verify , as I noticed that (only) this is what is tested here: the input-verification class.
Running my test still passes it, so there’s a good chance refactoring did not break it.
I can now go ahead and write the next test, probably this one:
METHOD one_werks_belongs_NOT_to_bukrs. "then "when cl_abap_unit_assert=>assert_false( M_cut->VERIFY_WERKS_BUKRS( iv_bukrs = '0202' it_werks = value lcl_input_verification=>ty_werks_list( ( werks = 'W01' ) ) ) ). ENDMETHOD.
It’s worth mentioning the “writing” that test is now very little effort (ya’ll know that I use AdT here, right?):
1. Highlight the first test (using mose or keyboard).
3. Change the value behind ‘ iv_bukrs = ‘.
4. …oh and strg+1 + click to create the “definition for testing” with quick-fix.
I went for it and did TDD – it took some time, but it gives me so much confidence for future refactorings and it will work as safety net for future changes.
It will help me finding bugs early, or – even better – not even introducing them into my code! Of course, this will save me a lot of time, compensation for my up-front investment!
*Joachim standing there, proudly showing his now-UnitTest-secured code, smiling happily and giving the ‘thumps up’! *
OK, I admit it: I just copyed the textbook here!
The actual conclusion is:
I went for it and tried something new.
As often, this took a lot of time and I experienced new things.
I now know more than I have before.
Next time I add UnitTest to something, I will most likely be faster.
I know have a real reference (in my dev system, as opposed to the OpenSap sandbox), to which I (and maybe others) can refer to.
While I still can’t answer ‘yes’ to “is all of your code 100% covered with UnitTest?”, I can give that answer to “do you do (or did you ever) real-life Unit-Testing?” now!
Learnings (some of those are ‘no-brainers’ – still I re-discover them from time to time):
– Things ‘learnd’ once (OpenSap) quickly fade from mind, especially if not used.
– unplanned things take some time, too (e.g. Eclipse, ADT Updates..)
– it is a good idea, to follow tdd-cycle by the letter ( r_my_result = abap_true -> makes the first test pass). It helps to have only _one_ problem/task to solve at any given time. More might be overwhelming!)
– ABAP Unit works with local classes, it’s just not so nice. (You should structure your code using includes (as you should do anyway), creating a least one for test-classes).
– solving something in a not-yet-optimal way is better than not solving it; it’s the first step on the way towards the optimal solution.
Call to action:
Go ahead, learn and try out new things!
(It’s ok if you start small!).
Do talk about it, if you like!
As always, your Input is much appreciated!
Using local classes certainly presents some challenges. In my view that’s one of the biggest problems with OOP in SAP, because in other languages there is no concept of a global class, we have classes in packages. They are cheap, when you create one you are not afraid to step on someone.
In SAP when you create a class, or a Function Group or a Program, you are claiming ownership of that name, and that’s very costly.
It’s something core to SAP, that I don’t think SAP will ever be able to change.
EDIT: SAP Community, please fix the "edit" bug.
Thanks Joachim Rees for the great blog and reminding us that we shall do two things
Thank you for sharing Joachim.
I had a very similar experience when working with TDD and unit testing for the first time.
It is difficult the first few times through, but I am convinced it will pay dividends on the long run.
Thanks for sharing your experiences. Interesting to read.
Thanks you all for your kind feedback!
(I added a few more unit test after writing this blog e.g. one testing "2 WERKS, only one of them does belong to BUKRS" ).
Coming back to this report today, I think having (at least one of my classes) under some kind of unit test, does give me some kind of confidence when refactoring!
Not related to the topic at all, just coincidentially to that report: I had written it under SAP_BASI 750, and then had to port it down to 731 , so I now have some examples of modern (750) vs. slightly older (731) ABAP syntax, I can share here:
...well, that was a good read!
(I honestly completely hat forgotten about this blog, so it was nice re-reading it today!
General good idea: read stuff you wrote in the past (e.g. old E-Mails, explaining something) I'm often pleasantly surprised how much knowledge I already had back back then - and I joyfully re-discover things )
And again I re-visited this blog.
While this is true, I had a hard time finding it again! 🙁
(I try to not give identifying information in my blogs code/screenshots (Sys-Id, full names...) which is generally a good idea, but hurt me in this case).
What helped me in the end was report AFX_CODE_SCANNER -> whit it, I could search for "one_werks_belongs_to_bukrs" ( or broader: for ' " given ') , to find my (and maybe other) unit-tests again.