Actual TDD in Real Life
The claim is that nobody ever does Test Driven Development in real ABAP life so I have volunteered to give it a go in my day job.
Unbreakable
I have never ever done TDD outside blogs. It works really well, but experimental programs are not real, and not what I am paid to do. I do these Monster related programs in my spare time and then blog about them to prove some sort of point, whatever I am passionate about at the time.
Jelena raised some very real concerns about in her blog:-
https://blogs.sap.com/2018/03/23/tdd-electrolytes-and-double-boiler/
The pertinent point is that all the examples are trivial in virtually every article you can read in a book or on the internet. What is required to get this Monster to be coming over the ABAP hill?
What are needed are better examples clearly – this is what happens at SAP conferences. The most successful presentations are not the ones where SAP personnel (I am sorry HUMAN RESOURCES) get on stage and say how great the company that employs them is.
The presentations people really want to see are from other companies that have implemented whatever SAP thing they are thinking of implementing and want to hear what went wrong and how difficult it was and did it really work in the end. The answers usually are that loads of things went wrong and it was much more difficult than expected but that it DID work in the end. If it did not the speaker would not be presenting at an SAP event!
So I said in a comment at the end of this blog:-
https://blogs.sap.com/2018/03/24/open-sap-course-unit-testing-week-two-tdd/
- that I would do this for real at work on my very next task at work i.e. putting my (salary) money where my big fat mouth is.
The application I am working on resembles VA01 and so cannot be accused of being unrealistic in the way that Monster examples or SFLIGHT examples or “money machine” examples can be accused of not being relevant to the daily life of an ABAP developer.
I imagine the vast majority of organisations use VA01 and the developers are familiar with the procedural monster which is SAPMV45A.
I together with colleagues am currently rewriting a procedural application that “wraps” the sales order BAPI in an OO way. So the end result will be virtually 100% different code wise, only the business logic will be exactly the same as the legacy application (legacy as in it has no tests).
Culture Shock
NB This is only possible due to corporate culture. In my company top management look to the future, and that filters down. Many companies only look to the next quarter, and if the CEO only cares about the next quarter it is likely the CIO will be focussed on how much IT stuff can be delivered in the next quarter and the future can look after itself. If squirrels thought like that they would die out as a race, but luckily they are smart enough to plan for the long term. So why cannot most CEOs of large companies be as smart as squirrels? Maybe it is because the stock market analysts force them to behave that way, constantly focussing on short term results. The Squirrels are laughing at as humans for being so short sighted.
Analyse This
What I am currently engaged in is to analyse the existing code, one screen at a time i.e. do not try and boil the ocean. My initial approach is one test class per screen. A DYNPRO screen could be described as a “process” in that it has a defined purpose i.e. to capture data and do something with it for whatever purpose.
The purpose of the initial screen in VA01 is to capture the document type and a bunch of organisational information and then forward that to the main screen.
I look through every line of the “legacy” screen and make a list of everything the screen currently does (filtering out anything obviously insane). If you do not think there is anything insane lurking in your ten to twenty year old ABAP applications then you are in for a shock.
Once that code has been analysed you end up with what is called is the IT SHOULD list, a concept invented by Dan North. Dan, Dan the Unit Testing Man. Dan Dares, TDD Pilot of the Future.
What do I mean by that? (The IT SHOULD list, not the silly jokes) “IT” is the equivalent of what the program should do which was presumably was specified in the original specification by the business analyst (if there was such a specification and not just a post-it note). David Bowie invented post-it notes by the way, in the same way “The Monkees” invented TIPEX and that Gerry Rafferty invented the Self Advancing Date Stamp. Another fun fact is that UK “Bullseye” Game Show Host Jim Bowen played the saxophone on Gerry Rafferty’s “Baker Street” as well.
Will you just stop messing around on focus on the problem at hand?
This is the first problem – I am going to concentrate on the first process – the “initial screen” which gets processed before any actual business object is created.
I am not doing this 100% properly as I had created a skeleton framework before I decided to start on the TDD experiment, but the skeleton does not actually do anything as yet, so the tests are designed to force creation of the production code to put the flesh on the bones. As will be seen TDD will make me realise how illogically my skeleton was put together, with the leg bone sticking out of the skull and so forth.
Therefore the initial screen methods are all static, as no instance of the business object has been created as yet, so at first glance seem impossible to test. Let us make the list anyway, and then try to change the design so it is testable.
Your Kiss is on my IT SHOULD list
The IT SHOULD LIST then gets arranged by me into several categories:-
Derivations – default certain values based on business logic or the database
Validation – Missing Data – Prompt User to enter mandatory values
Validation – Dubious Data – Prompt User for Confirmation
Validation – Incorrect Data – Raise Error, Inform User
These are based on the BOPF programming model. At this point the only “action” (user command) is pressing the ENTER key so I am not going to bother with that at this time,
So I range the IT SHOULD list for the initial screen in the order above, and then further sort the list by putting the most important ones at the top of each category.
The tests to be written first (according to Dan North) as based on one criteria which is – what is the most important thing your program DOES NOT currently do?
To try and make it real here is my IT SHOULD list. The initial screen is where the user enters the order type and various organisational elements, just like VA01.
IT SHOULD…..
– derive the initial values of the organisational fields based on the PIDs of the user
– derive the text descriptions of the organisational fields
– force the user to enter a sales group for countries where this is mandatory
– prompt the user to confirm if service division is entered
– stop unauthorised users creating sales orders
– stop the user entering a document type which does not relate to a sales order
– stop the user entering an order type which has been blocked
– stop the user entering an invalid combination of organisational elements
– stop the user entering an order type not allowed to be used with the organisational elements (per customising)
Let’s Go Unit Testing Crazy!
So now I have solved the insoluble problem of knowing what to test. It is very tempting to write all the test outlines at once, but that is not the way it is supposed to work – you are supposed to do one at a time. Amazingly the idea is not to think ahead too far e.g. try and cater for all sorts of unusual situations or future requirements, neither of which may ever happen. This is the YAGNI approach (You Ain’t Gonna Need It).
Next problem is the thirty character limit on method names. I get around that my putting a big comment before each method name.
CLASS ltc_initial_screen DEFINITION FOR TESTING
RISK LEVEL HARMLESS
DURATION SHORT
FINAL.
PUBLIC SECTION.
PRIVATE SECTION.
DATA: mo_cut TYPE REF TO zcl_sd_sofe_application.
METHODS: setup,
*——————————————————————–*
* Specifications : IT is the Initial Screen
*——————————————————————–*
“IT SHOULD…………………
“derive the initial values of the organisational fields based on the PIDs of the user
derive_values_from_pids FOR TESTING.
ENDCLASS. “ltc_initial_screen DEFINITION
OK for the moment we ignore all the other requirements and do not rest until we get the very first test to pass. In fact it would pass at the moment because it is empty, so we need to flesh out the code a bit with the GIVEN / WHEN / THEN pattern.
This is taking a lot longer than the fifteen minute cycle described in the course, and it should not be this hard as I am supposed to have half an idea of what I am doing!
Look how much code there is already:-
METHOD derive_values_from_pids.
given_user_with_uk_pids( ).
when_screen_is_shown( ).
then_org_values_match_pids( ).
ENDMETHOD.
METHOD given_user_with_uk_pids.
ENDMETHOD.
METHOD when_screen_is_shown.
ENDMETHOD.
METHOD then_org_values_match_pids.
cl_abap_unit_assert=>assert_equals( exp = ‘XXXX’ “Desired Result
act = ms_initial_screen_fields-vkorg
msg = ‘Sales Organisation is Incorrect’ ).
ENDMETHOD.
The amount of test code will only get bigger and the irony is the actual production code is three or four lines all starting with GET PARAMETER ID. If it was only one line I would test it anyway! That’s madness! When Batman goes to Arkham Asylum does he find the padded cells full of TDD developers?
In any event I have now got the stage where my test is complete enough to fail! I press CTRL + SHIFT + F10 (or take the menu option (Local Test Classes -> Unit Test) and I get a red light and a message telling me what assertion has failed. Just as an aside as I am doing this for real, I have to work on a system where ABAP in Eclipse is not yet available, though it will be in a month or so when it gets upgraded from 7.02 to 750.
The THEN part was easy enough, but I immediately get into trouble with the WHEN part. There is literally nothing I can call in my existing code which does just what I want, which means I have my methods doing too many disparate things. In this case the initial design was a series of calls to methods of helper objects. This was clearly mixing up the WHAT was going on with the HOW i.e. the implementation details i.e. what helper object was doing what.
This is an unexpected side effect to the TDD process – you are forced into an OO design. Actually I imagine that is what some people are going to hate about it, along the “not everything can be test doubled” lines.
In any event I have come to the conclusion that the changes I was forced to make to my class to make it testable improved the design. It helps it is a brand new class, but nonetheless it would help with existing classes or even forcing classes to be used in existing procedural code (which is not that difficult).
Test Double or Nothing
One thing you realise in a hurry is that CREATE OBJECT commands are poison to the unit test process. You always have to have the actual instances injected into the code where they will be used, or created by some sort of factory, thus enabling them to be test doubled.
In this case I had the actual instance of a view created a few lines before the DISPLAY method was called, and thus there was no way to test double the UI. I need a test double view instance because I do not actually want a screen to appear, and I want to fake the user input. I will need a fair few other test double classes as well, but the rule is to write the least amount of code that will make the test pass, so we do one thing at a time, and right now the one thing is creating a test double UI subclass, inheriting from the real one.
Then I attempt to inject the test double class as a helper object into the real class. That took a lot of debugging and re-organisation to get that to work properly without dumping.
Still, even though I only have one test so far, during the “red” cycle when I am changing things to get the one test to pass, I soon found a totally unrelated problem in my logic, a problem I thought would never occur, but it occurred for the test, and it would have occurred in real life sooner or later. To be brief because a field was mandatory on the SAP GUI I had presumed you could not return from the view without that field being filled. However what if we had a different UI technology, or no UI technology at all and were getting called via a BAPI or something? Then the field might possibly be blank and the program would have dumped.
In essence it took me while a while to get from the test being red because I had not written anything, to the test being red because of problems with the test double UI object being injected, and unrelated problems with the existing code, which as I said, I had considered trivial. This proves what I said earlier, as soon as you have more than a few lines of code, maybe even one line of code, you have the potential for bugs. This process shines a spotlight upon them.
Anyway the first stage was to have a test double object for the UI where all the display method does is have the user press ENTER. I now need a way to fake the PID values being read and updating the data structure.
The eagle eyed amongst you will notice I am faking everything that is happening, so what exactly is it I am testing? Simply that a routine is called that reads the PIDs during processing of the screen. That is something the program should be doing (according to the requirements I drew up earlier, the IT SHOULD list) so I am making sure it actually does it.
Once again I find I had put the code to get the parameter IDs directly in the model, this needs to be abstracted to a persistency layer helper object, so it can be faked. It was not too difficult extracting the methods (with eclipse it would be even easier) though with everything on the initial screen being static, the injection process is really painful.
I set the test double derivation to return a hard coded value of VKORG equivalent to what the test is looking for. In the setup I inject the test double persistency layer, and magically the test goes green.
My Blue Heaven
The next stage is the blue “refactor” stage where once things are working you can make the code better and be sure you have not broken anything, because if you have then the test(s) will turn red again.
Making things “better” is of course highly subjective. I tend to equate “better” with “simpler” as in can I make the code clearer either by renaming something to make it more obvious, or reducing the lines of code – note if that comes at the cost of clarity then I might even favour increasing the lines rather than reducing them. Some of the new ABAP construct do not always make code clearer, sometimes quite the reverse.
Consider this example from the course instructors:-
m_cash_provider = COND #(
WHEN i_cash_provider IS BOUND
THEN i_cash_provider
ELSE NEW cl_cash_provider( ) ).
Is that clearer or more obscure than the following:-
IF i_cash_provide IS BOUND.
m_cash_provider = i_cash_provider.
ELSE.
CREATE OBJECT m_cash_provider TYPE cl_cash_provider.
ENDIF.
Or are both equally as clear and the new construct is being used just to prove there is a new construct?
Going back to the “refactor” stage, in this case since the test double is sending back a hard coded value, and I do not like hard coded values, and moreover I have a “give” method with no code inside it, I think I will use the “given” method to instruct the test double what values to return. After my forthcoming upgrade I could use CL_ABAP_TESTDOUBLE to do this, at the moment on 7.02 I have to do this manually.
I will give my test double persistency layer an internal table of users and PID values based on USR05 which is where they live at session start up. Then because my test double can have extra methods in the GIVEN method I will call methods to set the current user and add the PID values to the internal table.
METHOD given_user_with_uk_pids.
* Local Variables
DATA: ls_pid_values TYPE usr05.
mo_test double_pers_layer->set_current_user( ‘BLOGGSJ’ ).
ls_pid_values-bname = ‘BLOGGSJ’.
ls_pid_values-parid = ‘VKO’.
ls_pid_values-parva = ‘GB30’.
mo_test double_pers_layer->add_pid_line( ls_pid_values ).
ENDMETHOD. “given_user_with_uk_pids
I needed to fiddle with some definitions in the test method to get that to compile without a syntax error. Afterward, the test still shows a green light, so I know I have not broken anything.
So one unit test done which does not really prove anything except that a certain method gets called in the production code at the correct point. Also that took a lot longer than fifteen minutes, more like one complete working day all up.
However that is probably what you might expect the very first time you do anything properly, as opposed to playing around with half-baked experiments, or doing SFLIGHT examples, or testing that one and one added together really does return two (the most common example you find on the internet).
That was HORRIBLE – let’s do it again!
The acid test (if you forgive the pun) comes next. Will adding the next test be any easier than the first? It is testing a similar sort of thing (that a method is called) so maybe things will go a lot faster this time.
Creating the new test method and the GIVEN / WHEN / THEN methods was really fast, as they are all blank to start off with. I add my assertion so that the test fails.
So I get to the red stage within a few minutes. As an aside someone at SAP loves having things appear in alphabetical order rather than in a logical order. The test methods appear in alphabetical order run ABAP Unit shows the result, and of course in SE80 in the object list the methods appear in alphabetical order. You may find this difficult to believe what in the initial version of the “persistent object” framework you had to put the fields in your structures in alphabetical order!
Anyway I would prefer to see the test in logical order rather than alphabetical order, but there you go, what cannot be cured must be endured.
Since the WHEN is the same (when the initial screen is shown) it is a question of filling in the GIVEN method. I add two similar methods to set data, methods which do not yet exist. With Eclipse I could then generate the methods automatically, without it I manually create the definitions and implementations.
I also have to write almost twice as many lines of code due to not being able to use inline declarations yet.
Nonetheless total elapsed time for adding the second text and getting it to the green state was about twenty minutes, not far over what the coures instructors wanted. In this case no refactoring was needed.
Nether Regions
As an aside, in the ABAP editor you have the “Region / End Region” comments you can make around blocks of code, which can then be expanded or compressed, as in the below screenshot:-
Expanded
Compressed
RAISE EXCEPTION – It’s Breaking up the Nation
My next test will be more complicated as it involves making sure an exception is raised when a field is not filled in under certain circumstances. The exception class in question does not even exist yet.
I can still write the test though enough so it fails:-
METHOD then_missing_field_error.
cl_abap_unit_assert=>assert_equals( exp = abap_true
act = mf_missing_field_error
msg = ‘Mandatory VKGRP Not Entered’ ).
ENDMETHOD.
I need to create the exception, then CATCH it in my test method that processes the screen, and setting a flag if the exception is caught. The test will still fail because the production code does not throw the exception. The last stage will be changing the production code.
That leads me on to the fact that the production code I supposed to handle the exception itself – it should catch the exception and then tell the view what field is missing and get the view to force the user to either enter the missing data or CANCEL which terminates processing,
That is yet another problem which forces a redesign – in this case the error handling is inside the CATCH statement in the public method being tested. In order to propagate the exception I have to put the error handling in its own method, so it can be test doubled. In real life, it just continues so the screen pops up again, the test double just propagates the exception so it can be caught by the test framework. Oddly enough that is the opposite of the usual situation where the real class does something and the test double one does not.
Does that sound crazy? Instead of having all the code to handle the exception just below the CATCH statement instead to just have a one line call to a method that does the exception handling logic? Maybe it is, but it is just what good old “Uncle Bob” advocates – he says methods should do just one thing, and error handling is one thing, and so the purpose of the method and the error handling for the method should be separated. That suits me just fine in this situation.
I could continue with the next few tests, but by this point I have pointed out the general idea.
Conclusion
The aim of the game was to do TDD for real instead of silly examples that have no relevance to real ABAP developers.
So I did it at work, on an application that virtually all ABAP developers can relate to, except maybe those who work for organisations that do not sell anything, or even provide services for free like some charities.
My conclusion is that this is a lot of hard work at the start, but gets easier once you are over the initial learning curve. The positive thing is that you can feel your program getting better and more rock solid hour by hour, just as advertised.
Due to the fact I am allowed to do this sort of thing I can honestly say I foresee a future where changes to one part of a huge program do not break functionality in another part. That has never been the case before.
Furthermore Robert Martin’s quote “QA should find NOTHING” by which he means no bugs once the code goes to the test system, is also possible. Which is not to say they should not test it anyway.
As might be imagined, I am biased from the start, and even though this process was a lot harder in real life than in silly Monster blogs, I still recommend it wholeheartedly.
Cheersy Cheers
Paul
PS I a have mentioned many times I am a big fan of the "Head First" series of books on software e.g. "Head First Design Patterns".
Chapter 8 of "Head First Software Design and Development" is all about test driven design, and although the examples are all in Java the concepts are 100% applicable to ABAP as well.
As mentioned before their position is that the more unit tests you have and the more TDD you do, the more money you make. They present a pretty compelling argument. It goes against my UK nature to view everything in terms of money, but I understand a large portion of the world thinks that way, so you have to view the world through other peoples eyes every so often.
Thank you for the real world examples. I still have a hard time wrapping my mind around TDD.. I do test as I write the code. So it's a real change. Perhaps I'll even try it - when I get some time!
Michelle
Hello Paul,
thank you for your insights. This helps a lot to start the TDD game!
If I understand this correctly:
you want to test a use case where an exception MUST be raised.
If you want to avoid using a flag you can call cl_abap_unit_assert=>fail( ) after the method that should raise the exception:
So if the test is successful the assert is never reached.
Best regards
Markus
The problem I have is that the public method I am testing of the CUT usually handles the exception itself.
So either I need to take the code that catches the exceptions out of the method I want to test, which is a possibility, but is a fairly radical redesign, or I have some sort of exception handling object which can be faked.
The real one would mirror what the current exception handling code does, the fake would just propagate the exception, enabling it to be caught by my unit test.
To be even more precise what I am doing in the method under test is that of a "controller" in an MVC application. The controller has a loop where it tells the view to call up the screen and returns with the data the user has entered
The controller forwards that data on to the model then which then evaluates that.
If all good the loop ends, if an exception occurs the model puts the error in an error log object and raises an exception caught by the controller. The controller has the loop restart, sending the newly created error log object to the view to tell the user what went wrong.
This is intended to keep the view "dumb" - all it does is just display the data and return what the user entered (or any user command). The model has all the smarts, but it knows nothing at all about the view. That way the UI technology can be swapped effortlessly.
But it does mean the exception handling is inside the controller method. If anyone can think of a more elegant way to do this, I am all ears.
Cheersy Cheers
Paul
I'm not sure whether I got the complete picture now but for me it sounds fine if the conroller takes care about the exception handling.
The model is responsible for the evaluation of the data and provides the result. The way this result is handled depends on the environment where the model is used. Maybe an error message should be displayed on a UI or it is processed in the background and should be written in the application log or a workflow event should be triggered ...
Thanks for the post, Paul! Before I forget, I agree with you regarding the readability of the COND statement. In this particular case it's in no way better readable or clear than the old-timey IF... In fact, I'm getting rather worried that some folks seem to be obsessed with "how much code can I stuff in one command" and it doesn't look much better to me.
I certainly appreciate your efforts here but honestly going full TDD still seems like a huge waste of time to me. Does anyone have doubt about GET PARAMETER ID not working correctly? The only way to mess it up would be to mistype the ID. Does one really need the whole method to find this out? Also this just returns whatever the user puts in their profile. If a user is in the UK but they (or someone) puts a US or German Sales Org ID in their profile then that's what's going to be returned. And it's not a programming error. Doesn't this actually fall into one of those categories where unit test is a moot point?
Actually I had the same "doubt" recently when I was making a change in the program and was thinking how could I apply unit test to it. The change was related to the currency conversion (see my SCN question on this). The old code didn't work because it did not consider the internal/external format differences for the currencies like JPY. So it was taking 50 USD and converting it to 30 JPY instead of 3000. But unless you look up the exchange rate there is really no way to tell that the program is wrong. It converts one number to another quite successfully. And if you have JPY you're getting a great deal as well. 🙂
I know this whole thing is about some kind of paradigm shift and maybe I just have not smoked enough Kool-aid yet but I remain rather skeptical on this subject, sorry.
Once I got used to COND, I honestly found it simpler than IF... THEN... ENDIF. I do agree however that it's entirely possible to go to far an make unreadable code.
Re: TDD. The issue is not whether you doubt GET PARAMETER ID will get it wrong. The point is that after you've written your tests showing that it gets it right, if someone comes along and changes that part of the program (perhaps your future self) and makes a mistake, you'll pick it up as soon as you run the test.
If my future self can't even get one line right it's time to retire. 🙂
I think the time to retire is before we get to that point! Conversely, some programmers I know should have retired long ago...
I think your currency scenario is a perfect example of why unit testing is useful. Back when it was written your application no doubt considered JPY, and several other scenarios would not have been possible to test. These are exactly the scenarios that unit testing CAN test.
"But unless you look up the exchange rate there is really no way to tell that the program is wrong."
-> And that's exactly the point of dependency injection. The dependency is the exchange rate, and by injection you're secretly substituting your converter's lookup function with a rate of 100:1. Then you know the rate during the test and can verify that 1USD = 100JPY.
This however only works if whoever creates the unit testing code is aware/remembers the JPY particularities, namely that it doesn't have decimals and actively codes a test for that. If nobody thinks about this beforehand, it will go undetected, but if somebody realises this for unit testing purposes s/he should also notice the current code's shortcomings and fix it right away. Isn't this a bit of a catch-22 where you'd need unit tests for stuff you haven't really thought about but don't code them exactly because of that?
...and that is precisely the other reason why unit testing is a good thing. It forces you to think about code in a more structured way. What are all the edge cases? Do I need to handle them? Make sure those I don't handle will fail.
They talked a bit about the same idea in the last section of the course - that it changes your programming habits. It's a two way benefit: the act of creating the test already improves the code, and this has been my personal experience too.
Hmmm, how do you then avoid overthinking this and chasing after the nitty-gritty stuff but forgetting about actually more important things - basically an "ignoring the forest for all the trees" kind of issue?
The whole thing may indeed be a complete waste of time, but I am having great fun. This injection business is a hundred times harder in real life than in monster mock-ups. The only way to learn anything properly is to do it for real and make a complete hash of it.
Still the point is that you can easily radically re-jig the design of the production code again and again and be sure you have not broken anything that worked up until now. That is going to be really important on my current project as the proposed design is somewhat "experimental" to put it mildly.
I am finding one of "Uncle Bobs" observations to be true. The test code gets more and more specific, which causes the production code to become more and more generic, and thus simpler.
In regard to testing parameter Ids you cannot. It is a dependency and out of scope, just like any call to the database or shared memory area of any description. All I can test is that the routine that reads the parameter Ids is called. That sounds really silly, and most likely is, but I have on occasion been guilty of writing assorted methods or even FORM routines and then forgetting to actually call them from the body of my code.
At the moment I am still struggling with getting the basics working properly, no doubt eventually I will come across a more worthwhile example.
I started attempting a test double injection today, but eventually gave up as the effort required to refactor so it could be done was not justifiable for the actual change to functionality I was making. It wasn't directly related to the bit I was changing anyway.
I may revisit it later, and book it under “platform improvements”.
It’s an interesting bit of functionality in that it is very important to the development process, and there is a high price to pay if it goes wrong, so it probably will be worth it in the long run.
At least I got as far as replacing the public methods in a global class with an interface, so that I can inject a double later. Which is really easy to do when you’re coding in Eclipse, btw. Generally I feel any effort put into refactoring (making code better) is worth it, even if you don't do it all.
The change in functionality I was doing was to make an existing method do a little bit more. I applied TDD to it, so wrote the test first, and then had it fail when the method didn't do the "more". Then made the change so the test didn't fail.
I certainly feel more secure that this quite important bit of code will work properly once the users get their mitts on it.
I did my first bit of real TDD last week.I have a bit of code that checks whether a role (agr_name) matches a rule (a custom thing I won't describe). The requirement was to add further logic so that two rules could be ANDed together and applied to a role.
First, I refactored the application, so that instead of the rule checking logic being a method in the main class, it was a encapsulated in a separate new class. I then wrote that class using TDD. This is rather like week 4 of the TDD course, about enhancing "legacy" code.
As I've observed elsewhere, in the past I'd have written the logic, and then run the program to see if it worked, with a few unit tests. Which effectively is "guess at the algorithm and hope". TDD forced a few things on me
The guesswork was removed, and I'm confident that when my tester comes back from vacation, on this development at least, she won't be asking me why it does something it shouldn't.
Like you, I found the first test was the most painful. But given the rather simpler scope of my scenario, it's not surprising I'm talking about 30 minutes effort rather than a day! Subsequent tests were must quicker to write.
I just gave a one hour speech to the UK SAP User Group Developer SIG, all about TDD and ABAP Unit.
About half the people in the room had heard of ABAP Unit, only two developers did TDD and they worked for the same company. That was still two more than I was expecting. As I could have predicted they raved about it being helpful to them.
I am doing the same presentation at SAP Inside Track Rome in June, well an improved one I hope, I got the concepts out in a bit of a chaotic order this time.
I dug up an ASUG presentation from 2011 on TDD and this slide I foud quite interesting:-
Note that they say it is NOT about testing? That ties in with what I have experienced. In my initial test class I had nine test methods which in total described every single thing the screen being tested did. It could be argued that some of those methods were not really testing anyting e.g. the one that tested the PIDS.
What I did end up with however was a design that was ten times better (simpler, more modular, easier to change) than my first cut, and that would never have happened if I had not written the tests and had to fiddle around with the design to get them to pass.
Someone on the internet put it thus "if you write a load of test cases it makes your program better, even if you never run the test cases ever again". I get the point, but being able to run them after every change does give you regression testing for free.
Someone else on the internet said "the more I read about TDD, the more it sounds like hysteria"