Warnings:
- English is not my mother language (is not even a far long time ago lost aunt language), and I learned it just with the goal to be able to communicate, so, my apologies for all the atrocities you will find here
- there's a saying in my land that says something like "you can learn from a bad example", here comes one.
- no animals (except me) have been harmed during the building of the software exposed
- the pieces of code in this blog entry are invented, not tested (pun intended) and not designed to work, so do NOT use them, nor take them as a good way to do things.
Acknowledgements:
This piece of cr... information has been possible by my own effort and I want to thanks me first. I deserve kuddos and a raise, but I will be happy with just a bit of ice cream (cookies and cream preferred).
The idea of writing this blog entry is mine too, but has been inspired by the titanical effort of
enno.wulff, who even has created a
Git for examples about ABAP Units (I will not use it because I'm a lazy person who hates Git).
matthew.billingham, as the guy who thinks TDD are worth the effort (as I do, but I hate the word "effort") and has been an inspiration for me since I met him in the 2012 World Championship of Mornington's Crescent (that I won, of course, thanks to the Richard Harper's Tuly gambit).
bfeeb8ed7fa64a7d95efc21f74a8c135, who wrote books,
one with a rocket in the cover (that I bought), and has give me some hints about the work (I hate the word "work" too) I'm exposing here. He must know a lot about ABAP and coding the proper way, because he has a hat.
And every one who contributed to
this discussion in the Coffe Corner about Unit tests. I will not mention all of them because you will think I'm trying to write a book or something (in fact, I lost completely the notion of time while writing this... thing I don't remember what was about), specially
c436ae948d684935a91fce8b976e5aa7 because she liked the club, and
jelena.perfiljeva2, who has no time to learn TDD, but will do when able, I'm sure of it.
Introduction
This is the story of a journey. More than that, it's the tale of a brave hero who embarks in an epic time travel. It begins in the early 80's where only the music was a good thing, and ends... well, it's about you to find it or not by completing this read.
It's about self knowledgement, self awareness, self consciousness and selfness selfishness (and a bit about the Loch Ness, I want to visit some day).
In the eighties we had ABAP. And in the eighties we had R3 and some things called EXITs (as the current enhancements, but easier to find, use and abuse.
And some people decided the 80's were fine enough to not move to the XXIst Century, and kept developing things the same way for 40 years.
This is the story about a not-so-young hero who tried to change that.
MV45AFZZ
This is an include used to enhance some sales delivery transactions (not sure which ones). The enhancement works this way:
in the standard SAP code, there are calls to FORMs in this include in some points of the process, each one intended to allow the coder to mess with the data processed by those transactions.
This process is intended precisely to allow ABAPousaurus (more on them some other day) to fill those forms with a thousands of lines of code, with unlimited IF...ELSE nested blocks and dark, obscure code because yes, we hate you, future dude who will stole our job.
So, when someone asks you to add a single, little, tiny modification in the quotation-to-offer process, you must waste few days of your live trying to understand where your modification will fit, how to add it without breaking that toothstick Eiffel Tower that supports a two liters nitroglyceryine bottle.
Then you realize you cannot do it.
Encapsulation, OOP and the mole's mother
Step 1- put your code into a class
So I (the hero, if you have forget it) decided to encapsulate the whole include into a collection (of maybe one) classes, and leave all the two thousand line FORMs like this:
form userexit_move_field_to_vbak.
zcl_exits_for_sd_docs=>moveheaderchecks( ).
endform.
Amazing, right? I deserve a medal just because the intention.
But reality hits. And hurts. A lot. And you notice the process must evolve, because there are popups, and error messages and dark unicorns trying to bite your nose, and you want not to put popups, error messages and unicorns into a neat-wannabe class.
So, you go this way:
form userexit_move_field_to_vbak.
data: myclass type ref to zcl_exits_for_sd_docs,
messagetype type symsgty,
messagetext type string,
texts type zcl_exits_for_sd_docs=>tabtexts.
myclass = new #( ).
myclass->moveheader(
exporting header = vbak currentposition = vbap
changing newpositions = xbvap[] newpartners = xvbpa[]
messagetype = messagetype messagetext = messagetext
texts = texts ).
if messagetype is not initial.
message messagetext type messagetype.
endif.
if lines( texts ) > 0.
CALL FUNCTION 'POPUP_WITH_TABLE_DISPLAY_OK'
EXPORTING
endpos_col = 80
endpos_row = sy-tabix
startpos_col = 10
startpos_row = 10
titletext = 'some weird title'
IMPORTING
choise = lv_tabix
TABLES
valuetab = texts
EXCEPTIONS
break_off = 1
OTHERS = 2.
endif.
endform.
I don't really like this approach, I'm deciding if I move the popup and so into another class (and even change the CALL FUNCTION thing for another class, but hey, this post is not about my code, but about me.
Step 2- refactor (you will keep doing it the rest of your working life)
Then I copied the whole content of the FORM, and put it in the
moveheader method. A whole mess. But as I am a cool guy (although too modest to be noticed), I decided to keep working the proper way, and split all those lines of hell into other methods... to end with something like:
method moveheader.
case transaction.
when 'VA01'.
dothis( header ).
dothistoo( exporting header = header changing newpositions = newpositions ).
when 'VA21'.
dothis( header ).
checkthat( exporting header = header changing texts = texts ).
checkthattoo( exporting header = header newpositions = newpositions
changing messagetype = messagetype msgtxt = messagetext ).
...
endmethod.
So, in the future, my future me (or the poor guy who inherits my chair) will be able to know anything that happens just by looking this first method.
I know, we are supposed to deliver dark, complex, unreadable code to be able to keep our jobs (and, to be honest, to punish people who stole our jobs), but been myself in the past unable to understand my own code, I decided to keep my sadistic inclinations away from work. Apologies.
Apologies accepted.
Step 3- release to production and test it
THE WHAT????
Well, that was my sadistic side acting again, sorry. I needed a way to be sure all my refactoring did not break anything but my soul. So I needed a set of tests.
Doh! If I could manage to have those tests while building the whole thing, I'd be able to more-or-less safely release it to QA without messing with users yet.
Introducing TDD and ABAP Units
Let's stop for a bit. I'm sure you need a rest of all that wonderfully amazingness. Let's talk about how an intelligent mammal (an orangutan, a chimp or maybe even a human being) would do that work from scratch: Test Driven Development.
In a nutshell, because I want not to talk too much if it's not about me, TDD is a way to build software from tests. The what? Just keep it with me for a bit.
The TDD building process
Test list
First, you split your requirements into a list, and build a list of tests, one for each requirement. You split each test in as many smaller tests as possible. Then you add some other obvious tests to the collection (like "am I breathing?").
You sort that test list from the easiest (and obviousest) to the complexest (or the least obvious). Let's say you are creating a calculator with an "add" functoin. Your test list should be something like:
- two numbers are provided
- the result is the sum of both
But you must split those tests, and add some others:
- two parameters are provided
- check first parameter is a number
- check second parameter is a number
- check the output is a number
And then some other trivial ones, just for fun:
- one plus one is two
- one plus two is three
And now you can launch Eclipse and begin to work (hey! I've been working since I grabbed the first coffe before getting the requirements!). Yes, but your boss will not admit you are working if you don't have a matrix-like set of screens full of code lines, right?.
Building from tests
The process is easy:
- create a test
- fail it
- dirty-fix the code until you pass the test
- refactor (and test again for mind peace)
- rinse and repeat
So, you create a first test, let's say
test test_1plus1equals2
if ( add 1 1) != 2 kill_the_guy
You run the test, and it fails, because you don't have even the functionality. So you create the functionality (pseudocode in an invented language)
class calculator
method add importing first second returning result.
return 2
You run the test, and it works, You refactor the code (uh?) and test again. It's still working.
So you rinse, and repeat with a new test
test test_2plus2equals3
if ( add 1 2 ) != 3 kill_the_guy
And it fails (because 1+2 = 2 ... anything added equals 2 within our calculator, remember? we did it dirty).
So, you go to the code and put a sum on it:
class calculator
method add importing first second returning result
return first + second
And it works!! Now you can keep adding tests to the collection (what if no numbers provided, or whatever suits you the most). You add them one by one, you fail them (or don't), you fix and refactor, and ask your boss for a raise, because you are (now) worth it.
Congratulations, you are a decent XXIst century developer
ABAP Units in a nutshell
(in a nutshell because this entry is not about them, it's about me)
ABAP Units is a tool within SAP (in the GUI and in the eclips ADT or whatever the name is) that allows you to create "parallel" code to keep your tests. Basically, it's like having a second program that automatically runs your first one, does all the checkings and returns the errors without effort (except for the whole building process).
Imagine it: you build your program, you press Ctrl+Shift+F10 and a small screen appears with all the mistakes you've made. Yes, you want to cry in the corner (again), but at least you know what the heck you ruined this time.
Benefits of this approach
Using TDD you build robust and efficient tools..
WRONG! your tools will be as robust and efficient as always
So, what then? Why all that pain?
Safety
Basically, because during the building process you will be able to detect desing/requirements flaws, and because you will know at each step you are going the right way (to the delivery and the money!).
More safety
Because every time you (or that guy who will stole your job) change any bit of code, the code will be able to test itself, and tell you (or that evildoer) what you have break this time.
Even more safety
Because every time a requirement changes you can
change your tests before change your code. So your tests will guide you to the modifications to be make.
Uh?
Ok, let me explain. Imagine our user wants our add method to subtract. Just for fun, he's a user, and he hates you.
So, before you change your class, you change your tests. Just the first one to allow you to grasp it (now, add 1 and 1 must return zero)
test test_1plus1equals0
if ( add 1 1) != 0 kill_the_guy
You run the test, it fails, so you fix the class. Theoritecatelly (or whatever) you should dirty fix it (lets say "return 0"), but as you know the whole class, and you have changed all the calculated tests, you can change your class directly to the goal
class calculator
method add importing first second returning result
return first - second
You should add some interesting tests to the class now, because subtraction has some weird behaviour (like negative numbers and so). But it's up to you to follow this derrailed example. We were talking about me, my work and why am I that cool.
Back to the future - why my example is a wrong one?
Well.. let's assume that I finished my work. I build a lot of tests and passed all of them. My code now is safe, and I can probably release it to production knowing it will not break anything. Of course, by the time I'll do it, the requirements of the enhancement will be different, or my company declared bankrupcy, but (this time) it will not be my fault.
So, if I am delivering a safe (even safe) modification, why I am telling you this is a wrong example?
Because I did it the opposite way.
But this is not evil. TDD teaches us to move forward, and to build nice code (sometimes even cooler than that dude's t-shirts with fractals) to embarrass our colleagues when we tell them "yes, we do TDD and your shirt sucks".
But ABAP Units allow us to build tests after the work is done. That's what I did (or that what's I was doing before getting fired for spending too much time writing about me).
I made a massive rebuild of a critical chunk of code, and I am able to release it (to QM) with some degree of confidence. This time I don't need to pack my stuff before the release, and I will (I wish) not have that termination letter, nor the citation for the suing process my company does to me for breaking his systems.
And now I have a bigger chunk of code, because I have the original functionality, plus all those "method endmethod" lines, declarations and local classes for testing.
BUT my code (let me repeat it) it's safer than ever. And I will be able to change it the proper way (TDD) next time.
Conclusions
ABAP Unit is not only a tool for TDD geeks.
To
spend time learning ABAP Unit and TDD
is not a waste, it's an invest (do you get it, Jelena?).
To wear a hat not just protects you from sun burns, but also helps you to write nice books.
Epilogue
Here I am. Twenty years in the future, reading this entry and feeling amazed about how great I were. Almost as great I am now.
Since that discussion in the Coffe Corner, I've been working TDD, and I made a lot of cash while keeping my users and my colleagues happy. They love me. Even more.
So yes, TDD and ABAP Units payed the effort I put on learning them. And now, while I turn off the computer and go to my retirement surprise party (I will put a surprise face, and I will tell them "doh, it's not necessary, you could spend that money in charity... but thanks for the titanium watch"), I feel the proud of a work nicely done.
May be the change be with you.