Skip to Content
Personal Insights
Author's profile photo Bärbel Winkler

openSAP ABAP Unit Testing Course – some musings from another ABAPosaurus

Jelena Perfiljeva coined the term “ABAPosaurus” in her recent blog post ABAPosaurus Takes openSAP Course and it’s a fitting term for me as well. I’ve been in programming since 1985, the first 15 years with PL/1 on an IBM-mainframe computer followed by ABAP starting in 2000 when the company I then worked for (Kodak) switched to using SAP for almost everything globally.

So, long story short, I’m very much into procedural programming and (still) have a hard time wrapping my head around all things ABAP-OO. Nowadays, I don’t do a lot of programming myself and what I do is usually fairly trivial stuff along the lines of “select some data”, “enhance the selected data somewhat” and “create an ALV output from that”. My main reason to tackle ABAP-OO is to at least get some experience and to build a “passive” vocabulary in order to understand and review code written by others.

So, I decided to sign-up for openSAP’s ABAP Unit Testing course and have thus far worked my way through the first two weeks of lectures. I have access to a sandbox system at work so can try my hand at the exercises and play a bit with the provided code. One of the biggest benefits the course has is to learn about (for me!) new ABAP-constructs like DATA and VALUE to create and fill variables and the like on the fly.

Having said that – and even after reading the other blogs about the course like e.g. the one from Paul Hardy (here) or his comment on Jelena’s post (here) – I’ll still need “some” convincing that Unit Testing will in the foreseeable future become helpful to what I do. Here is my reasoning:

Whenever I write a new program, I do so in stages to a) always have executable code without any syntax errors and b) know that what’s already in the code works as it should. This is obviously not test driven development as I always have the code first, but it’s not that much different either as I don’t wait with testing until I have the complete program written. Here are the steps I usually take:

  1. Create a rough mental or written outline of what I need
  2. Start with an empty template via code completion which contains the main definitions, sections and routines I typically need
  3. Define the selection-screen and “play” with its look and feel until it looks usable
  4. Add the data selection for the “main” table and make sure that it works by executing the program with a break-point at the select-statement and checking the internal table’s content immediately
  5. Tweak the select if and as needed to get the expected result for the selections requested
  6. Add (or already have) the rudimentary logic in the code to fill the internal table for the ALV-output and trigger the output (this then makes it easy to see if the selection works and there’s no longer the need for debugging the SELECT)
  7. Add more logic to fulfill the requirement “in stages” by adding some code and executing the program to always see if the additional logic gives the expected results, like e.g. filling more of the ALV-columns with the correct data.
  8. Clean-up the code as needed, add documentation, text-elements and the like

So, I’m wondering if this approach is really all that different from test driven development?

Cheers

Baerbel

Assigned Tags

      25 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Fabian Lupa
      Fabian Lupa

      For the scenario you described I don't think test driven development or even unit tests at all make much sense. You could test if the SELECT works as intented with some kind of test data repository and an abstraction of the SQL statement (or some of the newer db mocking stuff), but otherwise: I wouldn't test the UI because propably it's just some kind of ALV which is a tested external dependency and UI testing in general is in my opinion most of the time not worth the effort. Also there are no external components that rely on the behavior of your report because it does not have a public API, so that use case also isn't there.

      Unit tests and TDD make much more sense when you are working on class libraries or whole frameworks that are intented to be consumed by others (/other applications). In these cases you most of the time don't even have a "consuming frontend" yet that could even execute your behavior, so unit tests really help there. And if you don't have tests and there suddenly are people consuming your APIs and find an error it get's really expensive to fix it.

      But let's say your report that selects some data, enhances the output table and displays it in an ALV also had a button in the output to do something with the selected line (mark as read or something like that). Then there would be some functionality which should be tested in my opinion. I would propably create a model class for the output line and add tests for mark as read working, mark as read not working if it is already read and behavior in lock situations. By doing that this model class automatically becomes somewhat separated from the report. If you make it a global class you could even use it from other applications and be sure it works because you have the tests to check.

      Author's profile photo Former Member
      Former Member

      Maybe what's worth adding to this topic is that not ABAP-OO is not only designed for big/complex/fancy stuff. Even the most rudimentary logic can be translated into OO - even a simple report with SELECTs, some business logic and display - that can or cannot be used into TDD approach.

      I too agree that your case is too simple and not worth the whole TDD ritual. But surely translating stuff to OO will make maintenance easier (especially if following the S.O.L.I.D concepts, separation of concerns) - like touching a small piece of the code and leaving the rest as it was, which actually aligns with the goals of TDD.

      Thanks for the article and keep it up!

      Author's profile photo Mike Pokraka
      Mike Pokraka

      Like the others, I don’t see a lot of scope for unit testing here. UI and database are the bits that cannot be tested and are therefore mocked in the unit tests. And those are exactly what makes up 80% of a typical fetch stuf from DB and show in ALV report.

      You could however unit test the small parts that do contain real business / application logic, such as calculation routines. If you write those in a local class, it’s also very easy to refactor into a global class if you need to reuse.

      I also find unit tests immense time-savers for debugging. Wrong value in report? Write a test for it, debug as often as you like with ctrl-F10 without having to fill in a 12 field selection screen to reproduce the problem across 15 debug sessions. Once you have that in place, it takes 30 seconds to add a test for your next bug.

      Author's profile photo Bärbel Winkler
      Bärbel Winkler
      Blog Post Author

      Thanks for the response, Mike!

      I actually tried creating a test class in a program I had recently written with local classes. But, I had defined the method where I thought testing could make sense as private which then prompted an error message that only public methods can be unit tested. This in turn would mean - if I'm interpreting this correctly - that I'd need to define all potentially test-worthy local classes as public. Or am I missing something?

       

      Author's profile photo Suhas Saha
      Suhas Saha

      This in turn would mean – if I’m interpreting this correctly – that I’d need to define all potentially test-worthy local classes as public.

      Ideally one shouldn't be testing private methods 🙂

      But if you need to, as in your case, you have to define the test class as a friend of the CUT.

      Author's profile photo Mike Pokraka
      Mike Pokraka

      Google "unit test private methods" and you will see there is a religious war raging about this.

      The course says no. Theory says that your public methods should call all your private methods, so testing them will automatically test everything.

      Another theory says if the private method is that big/complex that it needs a unit test then it should be an own class.

      I say focus on publics, the coverage analyser will tell you if something is untested, and if a private method needs testing then I will test it or refactor it. As Suhas said, you can make the test class a friend.

      class ltcl_test definition deferred. 
      
      class lcl_myclass definition 
                        local friends ltcl_test
                        create private.
      ...
      endclass. 
      
      class lcl_myclass implementation. 
      ...
      endclass.
      
      class ltcl_test for testing...
      "Now we can access private components
      
      Author's profile photo Sandra Rossi
      Sandra Rossi

      It's worth continuing the sentence "then it should be an own class" : and the private methods become public in this new class, so that they can be unit-tested (so we're still in line with the theory)

      Author's profile photo Mike Pokraka
      Mike Pokraka

      I assumed this was an obvious conclusion, but you're probably right it's worth mentioning.

      Author's profile photo Bärbel Winkler
      Bärbel Winkler
      Blog Post Author

      Thanks, Suhas & Mike!

      I’m however having troubles implementing that in my program. This is most likely due to my general cluelessness when it comes to all things ABAP-OO, so please bear with me.

      Here is the rough outline of my program:

      ....
      
      CLASS ltcl_test DEFINITION DEFERRED.
      
      .... Exception Class definition & implementation
      
      CLASS lcl_data DEFINITION LOCAL FRIENDS ltcl_test CREATE PRIVATE.
      
      ....
      
      ENDCLASS.
      
      CLASS lcl_data IMPLEMENTATION.
      
      ....
      
      ENDCLASS.
      
      *-----------------------------------------------------------------------
      * Test Class for some of the private methods
      *-----------------------------------------------------------------------
      CLASS ltcl_test DEFINITION for testing
      RISK LEVEL HARMLESS
      DURATION SHORT.
      
      PRIVATE SECTION.
      DATA m_cut TYPE REF TO lcl_data.
      
      METHODS setup.
      
      ENDCLASS.
      
      CLASS ltc_test IMPLEMENTATION.
      
      METHOD setup.
      "given
      m_cut = NEW #( ).
      ENDMETHOD.
      
      ENDCLASS.

      This shows an error for the “LOCAL FRIENDS” statement:
      “Forward declaration missing for CREATE (CLASS CREATE DEFINITION DEFERRED)”

      So, this seems to interpret the statement as if “CREATE” is meant to be another class. But, if I delete the terms, I get “A PUBLIC class can only be defined within a global CLASS POOL”.

      What is missing or what am I not understanding properly?

      I’m also confused about this information in the F1-Help for the LOCAL FRIENDS statement:

      “This statement does not introduce a declaration part. It must not be ended with an ENDCLASS statement.”

      Does this mean that there’ll have to be two CLASS lcl_data statements – one for the actual definition and another one to “introduce” the friends?

      I really wish this ABAP-OO-stuff was more intuitive than it is (or at least looks to me)! But this may be food for another blog post along the lines of “my gripes with ABAP-OO”!

      Cheers

      Baerbel

      Author's profile photo Suhas Saha
      Suhas Saha

      Hi Bärbel,

      LOCAL FRIENDS can only be defined for global classes 🙂

      For local classes LOCAL_FRIENDS is not allowed.

      "! Test Class
      CLASS ltc_main DEFINITION DEFERRED.
      
      "! Main Class
      CLASS lcl_main DEFINITION CREATE PRIVATE
            FRIENDS ltc_main. " --> Test Class is defined as a "friend" 
      
        PUBLIC SECTION.
        PROTECTED SECTION.
        PRIVATE SECTION.
      
      ENDCLASS.

      BR,

      Suhas

       

      Author's profile photo Bärbel Winkler
      Bärbel Winkler
      Blog Post Author

      Thanks, Suhas!

      After a bit more trial and error I finally got it to work!

      CLASS lcl_data DEFINITION
            FRIENDS ltcl_test.
      ...
      ENDCLASS.

      I now have a simple unit test in the local test class to check the processing of one of the local classes.

      *-----------------------------------------------------------------------
      *     Test Class for some of the private methods
      *-----------------------------------------------------------------------
      CLASS ltcl_test DEFINITION for testing
                      RISK LEVEL HARMLESS
                      DURATION SHORT.
      
        PRIVATE SECTION.
          DATA m_cut TYPE REF TO lcl_data.
      
          METHODS setup.
          METHODS user_no_developer FOR TESTING RAISING cx_static_check.
          METHODS user_is_developer FOR TESTING RAISING cx_static_check.
      
      ENDCLASS.
      
      CLASS ltcl_test IMPLEMENTATION.
      
        METHOD setup.
          "given
          m_cut = NEW #( ).
        ENDMETHOD.
      
        METHOD user_no_developer.
          "when
          DATA(developer) = m_cut->check_developer( 'USERID01').
      
          "then
          cl_abap_unit_assert=>assert_initial( developer ).
        ENDMETHOD.
      
        METHOD user_is_developer.
          "when
          DATA(developer) = m_cut->check_developer( 'USERID02').
      
          "then
          cl_abap_unit_assert=>assert_equals( act = developer
                                              exp = abap_true ).
        ENDMETHOD.
      ENDCLASS.
      

      Not that this does anything "spectacular" but now I at least have a working example. ?

      Cheers

      Baerbel

      Author's profile photo Mike Pokraka
      Mike Pokraka

      Great. Suhas is right, LOCAL FRIENDS only applies to global classes. Sorry for misleading, I missed the local class connection.

      Just for clarification: The compiler works sequentially, and if a class is not defined yet you cannot refer to it. DEFINITON DEFERRED just tells the compiler that this class exists, nothing more. It means we can now use it for attributes or other references (FRIENDS), even though the full definition is later on in the code.

      Otherwise it would not be possible to have two classes that reference each other in the same piece of code.

      Author's profile photo Suhas Saha
      Suhas Saha

      Theory says that your public methods should call all your private methods, so testing them will automatically test everything.

      I think i belong to the camp which says - "Test the public interface of the CUT".

      I say focus on publics, the coverage analyser will tell you if something is untested

      Bingo! +1

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Thanks for the mention! Just to clarify, ABAPosaurus is at least 2 years old and I actually stole it from someone on SCN. 🙂

      I got a chance to read more about TDD (and many smarty pants will be quick to point out that TDD and "unit test" are two different things) and the concept is that you write the test first and the actual code second. That's also how it's explained in week 2. To me this still seems utterly ridiculous but I'll have the whole blog soon to talk just about that (stay tuned 🙂 ).

      My process is very similar to the one in this blog. As far as ALV comes, in fact I mostly start with copy-pasting from another report. Pretty sure any ABAPer (or any programmer, for that matter) has at least a few decent templates to work with by the second year of their career. It's quite efficient and I don't see any problem with it as long as the template is well-written.

      There are still 4 weeks left in the course, so let's see how it goes...

      Thank you for sharing!

      Author's profile photo Michelle Crapo
      Michelle Crapo

      Gasp!!! You cut and paste - for shame. I'm sure no one does that. 🙂

      Really though - if it is the same logic a class could be used. Not saying I would write one. Just saying it might be time to think about it. Take a look at all the new stuff.  (Do as I would want to do. Not as I do.) But that is really changing for me. Starting to use OOP for about everything.

      Michelle

      Author's profile photo Joachim Rees
      Joachim Rees

      Actually I created an eclipse template to jump-start creating a new report. So I don't have to copy from "somewhere old"....

      (Not sure which exactly your point is, as you mention
      "another report" (I'd think: dedicate to solve some business problem)
      as well as
      "decent templates" (I'd think: dedicated to solely serve as a template).

      I have it in the back of my mind to sometime write  a little blog on it....

      best
      Joachim

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      I never really bothered creating a blank template (although at some point we looked into creating our own pattern in ABAP Editor) but I've mostly been the only developer so there is no one to argue about it anyway. 🙂

      For me "template" is just a word for a decently written program that I can use to start creating something similar faster and easier. Naturally, I don't just copy-paste the whole thing, that would be redundant. But in many cases even though the reports have different business purpose large part of the code is similar (e.g. selection screen, authorization check, ALV display, etc.).

      Come to think of it, "starter" (as in the "dough starter") would probably be more appropriate word to use here. 🙂

      Author's profile photo Joachim Rees
      Joachim Rees

      I have it in the back of my mind to sometime write  a little blog on it….

      Done: https://blogs.sap.com/2018/04/05/jump-start-your-report-writing-using-a-template-adt-eclipse/

      Author's profile photo Graham Robinson
      Graham Robinson

      So what you are saying is that another advantage of object-oriented ABAP is that it is easier to write testable code? ? #troublemaker

      Author's profile photo Michelle Crapo
      Michelle Crapo

      Write the test first? I'm with Mike - I probably would write the test after I found an error. Yes, this is too simple. I think when and where test cases are used is a good thing to think about.

      Perhaps take a look at the challenge from Ivan Femia and you will quickly see the need for unit tests.

      Also think about the times that you write a quick fix - perhaps even one line. Then do a quick check to make sure it works. Transport it to PRD, and find out you broke something. Having a unit test written would help with that. (Wouldn't eliminate it.)

      Switch to OOP. It just makes sense now. It will help for future upgrades. Yes, I understand that quick and dirty programs may really don't need it.  Just think about it.........

      Michelle

      Author's profile photo Suhas Saha
      Suhas Saha

      Hi Barbel,

      I am usually faced with this dilemma & i find it an overkill to write tests for something as trivial as your requirement.

      But as Mike has mentioned it won’t harm if there are tests for complex mapping routines, calculations et al.

      Add the data selection for the “main” table and make sure that it works by executing the program with a break-point at the select-statement and checking the internal table’s content immediately

      Just a small tip regarding testing the behavior & performance of the SQL statement. If you use Eclipse, then you can use the SQL console to do the necessary checks.

      BR, Suhas

      Author's profile photo Bärbel Winkler
      Bärbel Winkler
      Blog Post Author

      Thanks for the tip, Suhas.

      At the moment I'm just scratching the surface of Eclipse and already have a hard time remembering the keyboard shortcuts which - IMO - are somewhat lacking consistency with requiring either SHIFT+something or CTRL+something or even CTRL+SHIFT+something (was it "1" or "F1" or ????). So, I haven't yet progressed to anything like SQL console but will check it out now that you mentioned it.

      Cheers

      Baerbel

      Author's profile photo Mike Pokraka
      Mike Pokraka

      There is a reason for inconsistency: Long before ADT, shortcuts existed in SE80 and in Eclipse. And ABAPers were happy, and Java / C++ / PHP / whatever developers were happy in their own respective worlds.

      Along came ADT and it tries to balance both.

      My most annoying is F3 for forward navigation, which I'm now used to in Eclipse but it catches me out whenever I work in SE80 and it goes back instead.

      It's worth writing your most useful ones on a cheat sheet, there are already examples here and here.

      Author's profile photo Thomas Hammer
      Thomas Hammer

      Hi Bärbel,

      you describe the process you use for creating new reports. I hope you still following the course as we show how we propose to develop reports. The whole execution code goes into classes and can be tested as showed in the course. The only thing remaining in the report is the UI part and the sections creating and triggering the right classes at the right time. This would add the T-part of TDD to your described process - tests. Yes, you do manual tests, but this binds a lot of your or your colleagues time - every time there is a change in the code. And you can not be sure that somebody else breaks your code unintentionally because there are no automated tests watching over your code and your intentions for it.

      My co-trainer Jürgen also posted a blog to clarify our intentions for the course as answer on questions and opinions which came up on different platforms all over the place. Maybe you also find some useful information there.

      Hope you enjoyed the course.

      All the best, Thomas

      Author's profile photo Bärbel Winkler
      Bärbel Winkler
      Blog Post Author

      Thanks for your repsonse, Thomas.

      To be quite frank, "enjoy" isn't the term I'd use - but that's not the fault of your course. It's just that I have barely started to do some ABAP-OO coding with local classes and much of everything else flies straight above my head (see my older blog post about that)!

      My main objective at the moment is to better understand this "stuff" as I don't write a lot of code myself these days. So a somewhat "passive" knowledge is - thankfully! - all I need for now.

      Cheers

      Bärbel