Skip to Content
Technical Articles
Author's profile photo Frederik Hudak

Clean up your unit tests – introducing zcl_abap_unit_wrapper

Today I decided to show you how having a common base class for your unit tests might help you save some time and improve the readability of your tests.

The class I use simply wraps all non-deprecated methods from cl_abap_unit_assert. This means they keep the exact same signatures and just pass them on like this:

METHOD assert_equals.
    assertion_failed = cl_abap_unit_assert=>assert_equals(
      act = act
      exp = exp
      tol = tol
      level = level
      msg = msg
      quit = quit
      ignore_hash_sequence = ignore_hash_sequence ).
ENDMETHOD.

Let’s try to apply it to the test class used in my previous unit testing blog. A diff is worth a thousand words:

You can refactor existing test classes in two steps:

  1. Add inheritance.
  2. Use find/replace to get rid of cl_abap_unit_assert=> everywhere at once.

You can download the class from my blog’s git repository.

If you want to, you can take it and add your own often-used custom assert methods to have them accessible across your codebase.

Another advantage is easier testing of legacy code – you might not have certain assertion methods available on older releases, but you can pretend you do by replacing the cl_abap_unit_assert call in the base class with your downported version.

 

As always, hope this helps!

Frederik

Assigned Tags

      9 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Sandra Rossi
      Sandra Rossi

      The standard CL_AUNIT_ASSERT had the same usage but was superseded by CL_ABAP_UNIT_ASSERT which cannot be inherited any more. Isn't it because it's not a good OO pattern to inherit from such a class? (i.e. here the inheritance is a trick to easily use methods but it's a bad model) Isn't it better to create a class with a singleton pattern, and a static method to return it, in the "class_constructor" method of your test class you define a class attribute with a short name that may be used in all test methods? (x->assert_equals( ... ))

      By the way, is it really a problem to keep cl_abap_unit_assert? It's quickly written with the "assert" code template in ADT, and I don't think the class will be removed one day.

      Note: using "CL_ABAP..." in the title attracted my attention because it looked like a standard SAP class, but it's misleading. Why not using "ZCL_ABAP..."?

      sandra

      Author's profile photo Frederik Hudak
      Frederik Hudak
      Blog Post Author

      Hi Sandra,

      Thank you for your constructive criticism.

      It is a bad idea to leave the assert class non-final for its creators. If somebody does inherit from it and defines their own assert_true method, you can never introduce assert_true on the base class, because that would constitute a breaking change. In fact you can’t introduce anything new at all, since you can’t know who did what with your class outside of your system.

      It is quite possibly the exact reason cl_abap_unit_assert was made in the first place instead of extending cl_aunit_assert. In fact, if you browse the code, you will find out the old cl_aunit_assert is now actually also a wrapper for cl_abap_unit_assert!

      What I do is misusing the intended purpose of inheritance, but you should always consider your particular use case. Don’t blindly follow paradigms. If you could misuse inheritance to save a child from a burning building, would you not do it because it is not mentioned in any OO guidelines?

      Recently I glued three chairs together to make a “shelf”. This is not the intended use of chairs and you can’t even use them as chairs anymore. People say it’s amazing though. And it does fit my use case: I had too many chairs and no shelf.

      So let’s look at this particular use case:

      I don’t see much point for the intended use of inheritance in a test class. Having to jump around to some base class to get the full picture goes against readability of tests, which should serve as executable documentation.

      This means I’m left with an otherwise empty inheritance slot to I can use to get a different feature ABAP is missing – importing static methods. I can only do it for one class at a time, but cl_abap_unit_assert is a particularly useful one here. I’m sure you can find such imports in test classes in other languages, it’s just that ABAP doesn’t make this possible.

      The way you described is more OO-correct, but also more boilerplate.

      If ABAP introduces a better way, you will find another post from me entitled ‘You can now forget all about zcl_abap_unit_wraper‘. I agree the original title was clickbait without the Z in the class name. It now has one.

       

      All the best in your future endeavors,

      Frederik

      Author's profile photo Sandra Rossi
      Sandra Rossi

      Thank you Frederik for this detailed answer, I understand and I agree for the boilerplate argument.

      By the way, there's one drawback with your method, that you didn't mention: when an assertion fails, SAP displays the call stack where the assertion occurs in the test method but hopefully the standard removes all procedures from the stack which are part of the ABAP Unit framework. With the addition of this custom assertion class, the custom class appears in the stack. That would be nice if there was a way to remove that "boilerplate part of the stack" 😉

      Author's profile photo Frederik Hudak
      Frederik Hudak
      Blog Post Author

      Unfortunately, there is no way to do that, but you will quickly get used to going one level above in the call stack to look for the source of the problem.

      Author's profile photo Former Member
      Former Member

      Hi Frederik,

      thank you for sharing the wrapper class! I tried it out, but unfortunately the ABAP CI Plugin shows the wrapper class instead of the actual spot where the issue fails. Do you have an idea how to eventually mitigate that?

      AbapUnitWrapper%20not%20compatible%20with%20CICD

      Best Regards,

      Steffen

      Author's profile photo Frederik Hudak
      Frederik Hudak
      Blog Post Author

      Unfortunately I don't think this can be worked around, unless there was some standard wrapper class and the ABAP CI plugin was smart enough to exclude it from the stack trace.

      As compensation for not solving your problem, I will instead give you a related solution you didn't ask for which might be interesting.

      You can retrieve the current call stack programmatically as an internal table, and there you have an opportunity to filter it before showing it to the user.

      This would be useful if you were writing your own test framework or want to improve the error reporting of your application. To do this you can use the class cl_abap_get_call_stack.

      REPORT zfh_callstack.
      
      CLASS lcl_test DEFINITION CREATE PRIVATE.
      
        PUBLIC SECTION.
          CLASS-METHODS go_deeper
            IMPORTING level TYPE i.
      
      ENDCLASS.
      
      CLASS lcl_test IMPLEMENTATION.
      
        METHOD go_deeper.
      
          IF level > 5.
            " prevent infinite loop
            RAISE EXCEPTION TYPE cx_sy_dyn_call_illegal_func.
          ENDIF.
      
          DATA(call_stack) = cl_abap_get_call_stack=>get_call_stack( ).
          DATA(formatted) = cl_abap_get_call_stack=>format_call_stack( call_stack ).
          DATA(structured) = cl_abap_get_call_stack=>format_call_stack_with_struct( call_stack ).
      
          BREAK-POINT.
      
          go_deeper( level + 1 ).
      
        ENDMETHOD.
      
      ENDCLASS.
      
      
      START-OF-SELECTION.
        TRY.
            lcl_test=>go_deeper( 1 ).
          CATCH cx_root INTO DATA(cx).
        ENDTRY.
      Author's profile photo Paul Hardy
      Paul Hardy

      I took some cabinets which were intended for storing compact discs, turned them around 90 degrees, stuck them on the wall and used them for bookshelves.

      That worked great but it was not the intended use. The intended use of mobile phones was for making phone calls, nowadays my phone can do all sorts of things but when I try to call my wife I can hardly hear her.

      I don't think SAP will get rid of the current unit test class but you never know. They changed it once, they could change it again.

      I would note that in all the Java examples you inherit your test class from the central ASSERT class.

      It does make the code easier to read, which to me is such a huge advantage it over-rides almost any contrary argument.

      Cheersy Cheers

      Paul

       

      Author's profile photo Frederik Hudak
      Frederik Hudak
      Blog Post Author

      If we're gonna be stuck with ABAP for some time, we should at least try to make it a bit more bearable to use 🙂

      Author's profile photo Michael Gutfleisch
      Michael Gutfleisch

      Inheriting from CL_AUNIT_ASSERT or a similar test helper classes has semantically nothing to do with object orientation. It is just a technique to write readable test code.

      This way, test helper methods can be used without the prefix of a global class name like CL_ABAP_UNIT_ASSERT. We use the same technique also for custom assertions and other test helper methods as soon as we need these in different test classes.

      Test classes are only instantiated and used by the test framework. There is no other client code which uses the test classes and therefore using the inheritance is no problem at all.

      Code like cl_abap_unit_assert=>assert_equals (…) distracts the reader from the content of the test, because the first 21 characters do not contain any useful information.

      Any technique which avoids this boilerplate code is appreciated!