Skip to Content

An ABAP test case pattern using fixtures
inspired by Javascript

 

21.03.17 – Frank Gales

 

After years of ABAP development I some years ago moved to Javascript but I’m for a short intermezzo doing some ABAP development again. In Javascript in my team we do have a pattern for test fixtures that evolved during time. A test fixture is a set of test data.

Here is the pattern. It is mostly JSON so it should also be understandable for ABAP developers. In order to get the most benefit out of a test it should be easy to add a new fixture (test data set) e.g. to cover more edge cases or after a bug is found to have that specific context covered in a test.

 

[
  {
    testDescription: "we do have two distinct target mappings”,
      oInput: {
        parameter1: "value1",
        parameter2: "value2"
      oExpected: {
        result1: 4711,
        result2: {
        …
  },
  {
    testDescription: "we do have two equal target mappings",
      oInput: {
        parameter1: "value21",
        parameter2: "value22"
      oExpected: {
        result1: 1972,
        result2: {
        …
  }
].forEach(function(oFixture) {
asyncTest("getInbound … when " + oFixture.testDescription, function () {

   // the actual test code

})

Code Snippet: Fixture coding pattern in Javascript

[ ] defines an array of two objects { } – the fixtures – which each have 3 properties – like a structured data type in ABAP – testDescription, oInput, oExpected. testDescription describes the specific test fixture and is displayed in the test result. oInput is a an object – a structure in ABAP – that has all data that is needed for the test and oExpected is or are the values we need for the assertions. We iterate over the objects in the array and run the test for each fixture.

Now in ABAP things are more complicated. One needs to define structures and make things more explicit. Let’s consider a unit test. We would have a test class for each method to test. And we would have a ‘for testing’ method for each fixture. This methods do not contain the test code but only create the fixture and then call the actual test code which is contained in method ‘test’. BTW, my ABAP code style is influenced by being a Javascript developer.

class ltc_get_inbound definition " Tests method get_inbound of the productive class
  for testing
  duration short
  risk level harmless.
  private section.
    types:
      begin of ts_input,
        parameter_1 type string,
        parameter_2 type string,
      end of ts_input,
      begin of ts_expected,
        result_1 type i,
        result_2 type cl_productive_class=>ts_inbound,
      end of ts_expected,
      begin of ts_fixture,
        description type string,
        input type ts_input,
        expected type ts_expected,
      end of ts_fixture.
    methods two_disctint_target_mappings
      for testing.
    methods two_equal_target_mappings
      for testing.
    methods test
      importing
        is_fixture type ts_fixture. 
endclass.
 
class ltc_get_inbound implementation.
 
  method two_disctinct_target_mappings.
    data:
      ls_fixture type ts_fixture.
   
    " Arrange 1 – create the fixture
    ls_fixture = value #(
      description = 'we do have two distinct target mappings'
      input = value #(
        parameter_1 = 'value1'
        parameter_2 = 'value2'
      )
      expected = value #(
        result_1 = 4711
        result_2 = value #(
          ...
        )
      )
    ).
    test( ls_fixture ).
  endmethod.
  ...
  method test.
    data:
      lv_result_1 type string,
      ls_result_2 type cl_productive_class=>ts_inbound.
 
    " Arrange 2
    data(lo_productive_class) = new cl_productive_class( ).
       " I don’t like lo_cut. I prefer a name derived from the actual class name.
       " Here I used productive_class wich I would not use in real life code.
       " For the example it is okay.
    " Act
    lo_productive_class->get_inbounds(
      exporting
        iv_parameter_1 = is_fixture-input-parameter_1
        iv_parameter_2 = is_fixture-input-parameter_2
      importing
        ev_result_1 = lv_result_1
        es_result_2 = ls_result_2
    ).
    " Assert
    cl_abap_unit=>assert_equals(
      exp = is_fixture-expected-result_1
      act = lv_result_1
      msg = |result_1 is not calculated correctly when { is_fixture-description }|
    ).
    cl_abap_unit=>assert_equals(
      exp = is_fixture-expected-result_2
      act = lv_result_2
      msg = |result_2 is not calculated correctly when { is_fixture-description }|
    ).   
  endmethod.
 
endclass.

* Code Snippet: Fixture coding pattern in ABAP


Summary:

  • One test class per method to test (in unit tests, which is not the same then tests using ca_abap_unit)
  • ‘for testing’ methods create each a different fixture (test data set)
  • Method ‘test’ is getting called by the ‘for testing’ methods and does the testing.
  • Arrange, Act, Assert pattern helps to keep the overview
  • This fixture pattern makes it easy to add new test data sets

 

Add-On:

What if I need to deal with JSON as input and/or expected data?
Writing JSON in ABAP code is a pain. Splitting the JSON into ABAP strings which get concatenated is the usual way.
I do often create JSON using ad-hoc Javascript code in the browser console (Chrome preferred; the biggest one was 107,000 lines). Let’s say you have a few hundred lines of JSON.

Here is what I did. I added the JSON as comments in the code in the ‘for testing’ methods with some specific begin and end marker. As it is the fixture definition it’s where it belongs to.

method two_disctinct_target_mappings.
* begin extraction - marker: cl_productive_class->two_disctinct_target_mappings
* {
*   [

*     {

*       testDescription: "we do have two distinct target mappings”,

*       oInput: {

*         parameter1: "value1",

*         parameter2: "value2"

  ...

* end extraction - marker: cl_productive_class->two_disctint_target_mappings

With the following code you could extract the json:

data:
  lt_test_class_source_code type table of string.

data(lv_method_name) = |{ iv_class_name }->{ iv_method_name }|.
translate iv_class_name to upper case.
translate lv_method_name to lower case.

data(lv_test_class_include) =   
    cl_oo_classname_service=>get_local_testclasses_include( iv_class_name ).
read report lv_test_class_include into lt_test_class_source_code. 
find first occurrence of |begin of json in method { lv_method_name }|
  in table lt_test_class_source_code
  match line data(lv_begin).
assert sy-subrc = 0.
find first occurrence of |end of json in method { lv_method_name }|
  in table lt_test_class_source_code
  match line data(lv_end).
assert sy-subrc = 0.
delete it_table from lv_end.
delete it_table from 1 to lv_begin.
loop at it_table assigning field-symbol(<lv_line>).
  concatenate rv_json <lv_line>+1 into rv_json.
endloop.

One last remark:
The 107,000 lines JSON ended up in an eCatt test data container as even ABAP in Eclipse did not really like so big source code files. The drawback with that solution is that the fixture is decoupled from the test which I don’t like. But with such a big fixture – it is used to performance challenge our code – this seemed to be the more appropriate approach.

To report this post you need to login first.

2 Comments

You must be Logged on to comment or reply to a post.

  1. Christian Drumm

    Hi Frank,

    very interesting approach. Thanks for sharing.
    I’m not yet sure when I would use this approach. The reason is I usually like the descriptive names of test methods to show what is tested. However, when different test cases are only defined based on data I think this is a very useful approach.

    Having the JSON directly in the test method as a comment is really cool. Will definitely use this one when I need JSON as an input.

    Christian

    (0) 
    1. Frank Gales Post author

       

      Hi Christian,

      I agree with you rgd. descriptive names. Here is what I do.

      For unit tests each method (or class – depending on your personal preferences) to test has an own test class which has the descriptive name. I often name the test class with simply the name of the method to be tested.

      For integration or end-to-end tests the name of the test class also describes what bigger step / scenario is tested.

      With my approach the test class name reflects what is tested and the test methods names reflect the test set that is applied to the test.

      Best regards,
      Frank

      (1) 

Leave a Reply