Skip to Content
Author's profile photo Enno Wulff

Introducing: GLADIUS – A Test Unit Framework

One year ago, on June 10th 2017, Stefan Schmöcker and I presented GLADIUS on SAP Inside Track in Hamburg.

GLADIUS

The idea was to create a coding challenge platform like https://codewars.com or https://codefights.com. Only with ABAP. This idea turned out to be quite complex. That’s the reason why Stefan and I didn’t work any further on this project.

We now sat together to starting the next approach on GLADIUS. This is the first blog of hopefully many more about code challenges, units testing and learning.

Why GLADIUS?

Gladius is the name of the famous roman sword. Coding and fighting has a lot of similiarities: you have to work hard to become a good programmer, you have to practice, you need good tools. That’s why we thought that GLADIUS might be a good and appreciable name for a learning tool.

What is it all about?

Codewars or Codefights challenges you to write a function that returns the right result for a given input. You can code in many different languages like C++, Javascript, Ruby, php and others.

The function can be faulty so that you have to correct it or it can be completely empty so that you will have to code the complete function to return the correct result.

Unit tests

Your result will be checked by plenty of given unit tests. If there are wrong tests you must correct the function. If all tests are passed, you won the challenge.

There are visible and hidden tests. For visible tests you can see the input and the result. Hidden tests only tell you if it passed or not.

How do I know the right results?

There are mainly two types of challenge:

a) You will be given a detailed description of the problem. You will get examples and a lot of hints what the function should do and what not.

b) You only have unit test. You must derive the functionality by the results given in the unit tests. My favourite is this one:

1 = pump
5 = pump
10 = feet
12 = stomping

What are the results for 2, 3, 4…? 🙂

Back to ABAP

With this blog series I will try to tell you step by step what we are planning and what are the basics. At the end we dream of a magic workbench where complete workshops or courses can be defined, & tested, where you can create statistics and define or measure metrics and many more.

The following steps will show you the most simple framework to starting something like ABAP code fights.

The ingredients

All of the following objects are fully integrated in the ABAP workbench or ABAP in Eclipse. We will use the integrated ABAP Test Cockpit functionality.

These are the objects we need:

  • Interface which defines the method (importing and returning parameters)
  • A helper class to access different solution classes
  • A master class which hold the complete solution of the functionality
  • A class with all test units

The Interface

Define an interface with a simple method TEST_ME and the importing parameter IN and the returning parameter OUT.

INTERFACE zif_glds_demo_test
  PUBLIC .

  METHODS test_me
    IMPORTING
      !in        TYPE i
    RETURNING
      VALUE(out) TYPE i .
ENDINTERFACE.

This interface will be embedded in all solution classes that someone would like to create.

The Helper

The target is to have one class with test units and many solution classes of different people. But we do not want to implement unit tests to every solution class. We will create one super test unit class, the test master, and derive from this class.

To have access to the very class of the particular solution, we need a trick which we will implement in the helper class:

CLASS zcl_glds_demo_test_helper DEFINITION
  PUBLIC
  ABSTRACT
  CREATE PUBLIC .

  PUBLIC SECTION.

    DATA mo_class_to_test_generic TYPE REF TO object .

    METHODS constructor .
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS zcl_glds_demo_test_helper IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_GLDS_DEMO_TEST_HELPER->CONSTRUCTOR
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD constructor.

    TRY.
        DATA(lv_classname) = cl_abap_classdescr=>get_class_name( me ).
        FIND REGEX '\\PROGRAM=([^=]+)' IN lv_classname SUBMATCHES lv_classname.
        lv_classname = lv_classname(30).
        CREATE OBJECT me->mo_class_to_test_generic TYPE (lv_classname).
      CATCH cx_root.
    ENDTRY.

    IF me->mo_class_to_test_generic IS NOT BOUND.
      MESSAGE a000(oo) WITH 'unable to create class to test' lv_classname.
    ENDIF.

  ENDMETHOD.
ENDCLASS.

The class has the generic attribute MO_CLASS_TO_TEST_GENERIC. In the CONSTRUCTOR we will match the respective class to the test object.

The Test Units

Now that we have the helper class, we can code the test units. This test unit class derives from the helper class.

It is important that this class is abstract and that it is a test unit class!

The test methods (= unit tests) must be public and have the flag “testmethod” activated:

CLASS zcl_glds_demo_test_units DEFINITION
  PUBLIC
  INHERITING FROM zcl_glds_demo_test_helper
  ABSTRACT
  CREATE PUBLIC
  FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS .

  PUBLIC SECTION.

    METHODS constructor .
    METHODS test_0      FOR TESTING .
    METHODS test_1      FOR TESTING .
    METHODS test_2      FOR TESTING .
    METHODS test_3      FOR TESTING .
    METHODS test_10     FOR TESTING .
    METHODS test_99     FOR TESTING .
    METHODS test_random FOR TESTING .
  PROTECTED SECTION.
  PRIVATE SECTION.
    DATA mo_class_to_test TYPE REF TO zif_glds_demo_test .
ENDCLASS.



CLASS zcl_glds_demo_test_units IMPLEMENTATION.

  METHOD constructor.
    super->constructor( ).
    mo_class_to_test ?= mo_class_to_test_generic.
  ENDMETHOD.

  METHOD test_0.
    cl_abap_unit_assert=>assert_equals(
      exp = 0
      act = mo_class_to_test->test_me( 0 )
      msg = '0 should be 0' ).
  ENDMETHOD.

  METHOD test_1.
    cl_abap_unit_assert=>assert_equals(
      exp = 0
      act = mo_class_to_test->test_me( 1 )
      msg = '1 should be 0' ).
  ENDMETHOD.

  METHOD test_10.
    cl_abap_unit_assert=>assert_equals(
      exp = 90
      act = mo_class_to_test->test_me( 10 )
      msg = '10 should be 90' ).
  ENDMETHOD.

  METHOD test_2.
    cl_abap_unit_assert=>assert_equals(
      exp = 2
      act = mo_class_to_test->test_me( 2 )
      msg = '2 should be 2' ).
  ENDMETHOD.

  METHOD test_3.
    cl_abap_unit_assert=>assert_equals(
      exp = 6
      act = mo_class_to_test->test_me( 3 )
      msg = '3 should be 6' ).
  ENDMETHOD.

  METHOD test_99.
    cl_abap_unit_assert=>assert_equals(
      exp = 9702
      act = mo_class_to_test->test_me( 99 )
      msg = '99 should be 9702' ).
  ENDMETHOD.

  METHOD test_random.
    DATA(rnd) = cl_abap_random_int=>create( seed = 42 min = 10 max = 10000 )->get_next( ).
    DATA(result) = rnd * ( rnd - 1 ).
    cl_abap_unit_assert=>assert_equals(
      exp = result
      act = mo_class_to_test->test_me( rnd )
      msg = |{ rnd } should be { result }| ).
  ENDMETHOD.
ENDCLASS.

Make sure that the MO_CLASS_TO_TEST attribute referrs to the test interface.

The class to test inside the test units must be the MO_CLASS_TO_TEST attribute!

The Test Master

The test master class only implements the interface to have the structure of the test method and provides the correct functionality.

CLASS zcl_glds_demo_test_master DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    INTERFACES zif_glds_demo_test .
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_glds_demo_test_master IMPLEMENTATION.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_GLDS_DEMO_TEST_MASTER->ZIF_GLDS_DEMO_TEST~TEST_ME
* +-------------------------------------------------------------------------------------------------+
* | [--->] IN                             TYPE        I
* | [<-()] OUT                            TYPE        I
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD zif_glds_demo_test~test_me.

    IF in = 0.
      out = 0.
      RETURN.
    ENDIF.

    out = in * ( in - 1 ).

  ENDMETHOD.
ENDCLASS.

The function simply multiplies the given value by itself minus one.

Note that this class has local test units! But these test units are not implemented in this class but are derived from the global test units class.

CLASS lcl_test DEFINITION
  INHERITING FROM zcl_glds_demo_test_units
  FOR TESTING
  RISK LEVEL HARMLESS
  DURATION SHORT.

ENDCLASS.

Ready Player 1

This is the most simple framework to start implementing your own function:

Of course someone who wants to implement the function should not see the code of the master solution! You can provide a class which implements the interface and derives from the global test class.

Simply create a class like ZCL_GLDS_DEMO_SOLUTION_1, implement the interface ZIF_GLD_DEMO_TEST and create a local test unit section like the one above.

After activating all that stuff and running the unit tests via CTRL-SHIFT-F10 you will see that there are errors.

Next…

Maybe you can imagine that there is a lot of potential in this…

At least the administration design should look something like this:

Administrators can define different projects for workshops with different tasks. These can be tasks to new ABAP features, mathematical problems or customer specific functions.

Users can register for a project. Team leaders can see their achievements in overviews.

Test classes might be generated automatically.

The source code might be stored as a string in the database instead of a global class in the repository.

There might be challenges: Who provides the fastest solution to a task? Who uses the least number of statements?

Book authors might enrich their examples with GLADIUS test cases so that learners can interactively practice the books theory.

We could also think of a defined function (test interface) and the administrator simply enters the input values and the expexted results in the application. No need for programmed test class. This could bring test driven development to a complete new level. Imagine that your key users “write” the test cases… 😉

abapGit

All sources also on github: https://github.com/tricktresor/gladius

Tribute to Lars Hvam

See the 2nd part here: GLADIUS – The Next Level

Assigned Tags

      12 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Paul Hardy
      Paul Hardy

      Our key users do curretly write test cases - inputs and expected output - in a word document.

      If they could do that inside SAP that would be wonderful - however as I am sure you realise that is not a trivial exercise, due to the different data structures for each application, However just because something looks really diffcult at first glance is no reason at all not to puruse it as the "going to the moon" example proves.

      Cheersy Cheerrs

      Pauk

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      Thanks for your reply, Paul!

      Often SAP already provides a solution to complex things. The input of complex data can be done in SECATT data containers.

      I already wrote two (german) blogs about it:

      Use ecatt data containers

      Access ecatt data container

      Therefore the comlicated task would be to link the test method parameters to the right ecatt data container.

      Work? yes, of course. But no "rocket science".

      Regards

      Enno

       

      Author's profile photo Paul Hardy
      Paul Hardy

      On the SCN at the moment you cannot update comments once you have made them. This time I wanted to update my name which I spelt wrong, but got the ever popular "you have been logged out" message.

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      This problem has been reported 5 months ago, if not earlier. Apparently it starts working after a couple of hours, which is rather useless.

      Looks like this platform needs some kind of testing solution. 🙂

      Author's profile photo Volker Barth
      Volker Barth

      > Apparently it starts working after a couple of hours,

      I remember some CC members suggested that comment edits should only be allowed within a short timespan (say, 60 minutes) after the comment was made in order to prevent further replies from becoming stale...

      Probably the platform implemented just the counter-logic 🙂

      Author's profile photo Alistair Rooney
      Alistair Rooney

      Very nicely done Enno. This will be a useful addition to the team's toolbox. Vielen Dank.

       

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      Thanks Alistair! We will go on and improve when the days get cooler... 🙂

       

      Author's profile photo Joachim Rees
      Joachim Rees

      Hi Enno,

      I have never heard of codewars, but immediately when reading you blog, I thought of those ABAP KOANS: https://blogs.sap.com/2018/02/08/have-fun-with-abap-koans/

      It looks very interesting what you build here!

       

      Best

      Joachim

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      Thanks Joachim!

      habe a look at codewars.com and/ codefights.com. It‘s really great!

      ABAP koans by Damir Majer is something similar on a different basis (test unit and asserts). The goal is the same (similar): you get wrong code and you have to develop a solution that fits to the problem.

      in GLADIUS project the test units are „only“ a helper for achieving the goal.

      It‘s still in progress... 🙂

       

      Author's profile photo Raphael Pacheco
      Raphael Pacheco

      Nice project Enno Wulff.

      I liked of you make a project inspired on platforms for code challenge (e.g. codewars, hackerrank, project euler, etc...), it's a platform what I miss in ABAP. 🙁

      BR,

      Pacheco.

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      Thanks Pacheco! It‘s still some way to go... 😉

      Author's profile photo Jacob Taber
      Jacob Taber
      Pump up the jam, pump it up
      While you feet are stompin'
      And the jam is pumpin'
      Look at here the crowd is jumpin'
      Pump it up a little more
      Get the party going on the dance floor
      Seek us that's where the party's at
      And you'll find out if you're too bad