Quantcast
Channel: SCN : Document List - ABAP Testing and Troubleshooting
Viewing all 48 articles
Browse latest View live

Recording of Procedure to use External Variant in eCATT

$
0
0

 

SAP Box Attachment

 

Please note that these files are webex recording whose size exceeds 1 MB thus i am enclosing the files in the SAP Box link which is valid only for 30 days.

Thanks,

Aj

 


Continuous Integration - Automated ABAP Unit Tests in Hudson

$
0
0

In this document I would like to share my lesson learned and show that it is possible to run automated Unit Tests in ABAP and integrate it with Hudson (or Jenkinks) Continuous Integration server. With some development effort we can have all Unit Tests running every day, see tests results with code coverage in Hudson and look at nice graphs and historical data summary.

 

Document overview:

 

1. Introduction

 

When I started experience with SAP development almost 2 years ago, I looked for automated Unit Tests possibilities. Unfortunately Continuous Integration is not strong side of ABAP development. You can schedule and run tests regularly in fact, but the way how results are presented was not enough me. As I had already experience with Hudson projects built for java software (easy integration by the way), I tried to find out same possibilities in ABAP world. Unfortunately - no plugins for SAP integration with Hudson, not even one integration example existing in forums. I took the initiative into own hands.

 

 

2. Continuous Integration

 

Before I will go into details let me explain how I understandContinuous Integration process in ABAP context. It must be fully automated and consists of the following steps:

1. Compile and build code -  in ABAP it is actually done for free with activation.

2. Run tests, different levels possible:

  • Unit Tests - only these are currently supported in my framework.
  • Functional, Performance, Availability tests and others.

3. Collect and present statistics on Continuous Integration server - tests results and code coverage are available in ABAP.

4. Notify users in case of at least one test has failed.

5. Optionally deploy code - send to quality / production system - not included into my framework.

In the framework I focused only on ABAP Unit Tests automation, run in development not quality system because:

  • It reflects current development system status (we tend to have requests opened long time before they are send to quality).
  • Unit Tests run is disabled for quality systems in our landscapes.

 

Continuous Integration process should be triggered after each change commit, or at least once per day. For me once per day is enough (many developers and changes during day) and framework is configured as such.

 

Actually framework fulfills criteria from points 1-4, just with limited scope for Unit Tests in development system. Anyhow it is a lot.

 

 

3. Framework presentation

 

So let's look what we have. Framework is run for all Unit Tests in the system from customized packages (Z* or Y* packages). As I do not want to present statistics for all development in my company, I just show example results from ZCAGS_CI package, which is actually the package containing framework development itself.

 

Hudson projects for two development landscapes D83 and D87 are defined:

hudson_projects.PNG

  • From this overview we quickly see status overview: S - blue color means last success, W - project condition - stability, tests results, code coverage quality (sun or storm icon).
  • Projects are integrated in the way that they can be started from Hudson server - just by clock with green arrow button on the right.
  • Unit Tests Full Run Hudson project starts batch job in SAP system which runs tests, then it tries to download result files periodically and if they are found, code coverage and Unit Tests files are imported from SAP to Hudson server.
  • After that subsequent jobs Unit Tests - Package and Unit Tests - User are run. These jobs just read downloaded files and present them in graphs and statistics.
  • As you can see - all Unit Tests are running quickly: over 6 minutes for D83 and 3 minutes for D87. There are hundreds of Unit Tests available.

 

Example ZCAGS_CI package preview with Unit Tests results (real duration on test level is not implemented):

unit_tests_results.PNG

If someone knows Hudson from before he is aware of the package hierarchy and links to nested levels. We can click on example row to see its details. Lets preview ZCL_CAGS_CI_CODE_COVERAGE test results:

detailed_unit_tests.PNG

 

We see all 8 tests run from local class LCL_CAGS_CI_CODE_COVERAGE and all of them are passed. In case if there would be an error we could see it by clicking test method link further.

 

In addition Hudson keeps history of all builds and tests statistics. Graph below shows history of Unit Tests for all packages  (I have not shown number of tests which are normally on Y axis):

 

 

unit_tests_history.PNG

 

As you can see from the graph there were some failing tests that nobody cared about (red color). Having daily automated Unit Tests in place exposed that clearly so tests have been finally fixed. All run tests should be have success status - daily green build goal. In addition we see increasing number of tests due to Continuous Integration awareness and teams engagement.

 

Now lets look at code coverage statistics for ZCAGS_CI package. We see historical progress since beginning, code coverage finally increased till 71.2 % for line level:

code_coverage.PNG

Code coverage shows which of classes in the package were touched/entered during execution of Unit Tests. This is one of helpful statistics that measures testing code quality.

 

We see graph with historical data. 4 levels of code coverage are available:

  • Class - how many classes are covered
  • Method - how many methods are covered
  • Block - how many blocks/statements are covered. Block may be WHILE loop or IF statement.
  • Line - most detailed value, how many lines of code are run by Unit Tests.

 

Let me describe example of coverage based on two classes:

 

1. ZCL_CAGS_CI_HTTP_REQ_HANDLER

  • We see that there is not even one Unit Test case that uses this class
  • It is because request handler reads HTTP request and starts batch job or operates on file system, that is why I skipped Unit Tests there.

 

2. ZCL_CAGS_CI_REPORT

  • Main class handling report features.
  • 20 methods of this class were executed during Unit Tests run, class has 31 methods in total.
  • 113 lines of code were executed during Unit Tests run out of 180 lines.
  • We see that class coverage is 1/1. It is simple on class level, but if we see package level (header line) we have value 8/9.

 

Code coverage and unit tests views are available for all customized packages in SAP system that have executable code (top level view).

 

All these statistics can be extracted from SAP standard tools. Hudson integration just adds another, extended GUI view on top of results and makes tests run management easier. I can run tests at any time from Hudson. By default tests are scheduled on every working day. This is configurable in Hudson project.

 

It is worth to mention that we can configure Hudson project in the way that it sends automatic email notifications in case of at least single test failure occurred. We can have single person that monitors daily status or even whole team included into notifications - it depends on us.

 

Hudson offers even more options and plugins and is a good candidate to me for Continuous Integration server for daily automated tests.

 

4. Implementation overview

 

Main report looks like this:

main_report.PNG

We can specify defined packages list or load all Y* and Z* packages for tests execution. Second option is default, run from Hudson - we want to run all tests for customized packages in the system.

 

Code coverage is measured only for classes and excluded for programs. The fact is that reports often does not need to have Unit Tests or even cannot be tested. We have also many historical reports from time where there was no Unit Tests yet. That is why code coverage statistics including programs are very low. That is why we focus only on code quality measurements for classes - it is easier to focus and monitor improvements.

 

Two levels of results are possible - package level and user level. Package level is default, we see how well each package is covered with tests. User level is just for own overview and history monitoring. We cannot get exact user coverage as class and all its tests method is "owned" by creator, further updates from another user just adds statistics to original class creator.

 

After report is run, all results are saved to files with given names and path. Files are formatted according to JUnit format which is simple XML file with predefined schema and hierarchy. This is needed for integration with Hudson - we need to transfer files and read them on Hudson to show final results.

 

How results are calculated? That is the core of report, where I need to say thank you to SAP standard and its developers.

 

SAP development goes in good direction, Unit Tests framework is extended, new automated tools are available. One of them is report rs_aucv_runner, which actually can be run from SE80 -> right click menu on class -> Execute -> Unit Tests With -> Job Scheduling.

 

rs_aucv_runner.PNG

That is very good report that allows user to run Unit Tests for any packages, including subpackages. My customized report is built on top of this - submits standard program, reads required data through implicit enhancements during runtime and saves results to files. In fact we can get Unit Tests and code coverage results from this standard report, but to be honest results are not very informative - no information about tests count, separated code coverage for each class after selecting it and finally no history, just current results:

 

rs_aucv_runner_results.PNG

 

That is why I have chosen Hudson. It presents same data in different and better way.

 

It is also possible to schedule daily automated Unit Tests from SCI Code Inspector. However there is mainly one useful information there - failure tests. That is basically enough to be notified in case of tests failures. Anyhow, code quality statistics and historical progress overview is a good part of Agile development where reviews and retrospectives are part of life.

 


5. Summary

 

I hope this document will be inspiration for those who are searching for automated Unit Tests possibilities like I was before. I believe that Unit Tests are important and improve software development quality and efficiency as well. To encourage people to use them it is worth to establish daily automated tests in first place. If we add another layer of status visualization and progress history, it helps even much. We should believe that good quality software development requires automated tests that is why it is worth to build motivation with supportive tools.

 

Hudson ABAP integration is one of options. I started with Unit Tests, but I guess it would not be difficult to add another level of tests and present results on same server. Personally I found Hudson more user friendly. It is enough just to open HTML link instead of SAP transaction. Results are present all the time, calculated at night. They expose two most important statistics: Unit Tests failure/success rate and code coverage values, for all packages that we specify. If we have many teams, they can focus on own package monitoring. If we add history and statistics kept in Hudson, that is a good help for tracking progress and improve ourselves.

 

It would be good if SAP could provide built in integration to Hudson server as plugin. If someone is interested in details of development, I can share it or maybe even post new document or publish code project - I need to investigate possible options for code sharing/publishing rights if it is needed.

 

Good luck with automated tests in ABAP!

 

6. Update - source code

 

I was asked by many to public source code. Finally, after long time I got some capacity to do it - here it is. I put it to external server as zip file was not allowed on this forum:

 

ABAP Hudson integration.zip - Google Drive

 

Go to Readme.txt and try to implement framework in your system. You can contact me in case of any doubts. Good luck!

 

I preserve code property rights as mine, but you can use code for your own or company purposes, mentioning that the source comes from me.

SOAP Testing tool Configuration for testing Interface in SAP

$
0
0

Hi,

 

  SoapUI is a free and open source cross-platform Testing solution. With an easy-to-use graphical interface, and enterprise-class features, SoapUI allows you to easily and rapidly create and execute automated functional, regression, compliance, and load tests. In a single test environment, SoapUI provides complete test coverage and supports all the standard protocols and technologies. There are simply no limits to what you can do with your tests. The Document will help you configure the SOAP UI tool for testing.

Transporting a program Variant.

$
0
0

We normally maintain Variants in the Live system, but somewhere we may come across the scenario where we want to to transport the Variants along with program from Development system to the Live System or Testing System.

 

SAP has provided a standard program to pull a Variant into a tansport, in the doucment below, I will be showing the usage of the same.

 

 

 

Step 1 : Goto SE38 and enter program Name - RSTRANSP and Execute

 

Untitled1.png

 

Step 2 - Provide the program name and name of the variant that needs to be transported to next level and execute the report, constarint is that variant should be available in the system where this report is being executed (mostly Development system).

 

Untitled2.png

Step 3 -  Popup comes with program name and variant name which needs to be selected. Checkbox needs to be ticked.

 

Untitled3.png

 

Step 4 - Popup window for transport organiser appears where we can create a new Transport or use our own request.

 

Untitled4.png

ABAP Test Double Framework - An Introduction

$
0
0
      Dependent objects which can't be controlled makes writing unit tests hard or even impossible. In unit test environments dependent objects should be replaced with test doubles. They imitate the behavior of the real objects. The graphic below illustrates this idea.

 

Testdouble1.PNG

 

Until now test doubles classes had to be written by hand. This can be quite a tedious process. The ABAP Test Double Framework solves this problem and makes it easier to write unit tests for your code.
The framework is available with SAP BASIS release 740 SP9 and higher.

 

What is it

The ABAP Test Double Framework is a standardized solution for creating test doubles and configuring their behavior. The framework supports the automatic creation of test doubles for global interfaces. Method call behavior can be configured using the framework API. It is possible to define the values of returning, exporting, changing parameters and raise exceptions or events. Additionally, the framework provides functionality to verify interactions on the test double object, e.g. the number of times a method was called with specific input parameters.

Quick Demo Video

Getting started

In this document we use an expense management application as an example. cl_expense_manager is one of the main classes in the application which is used for expense calculations. Expenses can be entered by the users in different currencies and the expense manager has methods to calculate the total expense in the required currency. The expense manager uses an object of if_td_currency_converter to get the real time currency conversion rates and then calculate the total expenses.  For testing methods of cl_td_expense_manager, we have to make sure that the method calls on the if_td_currency_converter object return exactly the values that we expect it to return. Otherwise the unit test would fail because the values on which assertions are being done are dependent on the values returned by the methods of the if_td_currency_converter interface. To achieve this, first we have to create a test double object for the if_td_currency_converter interface and inject it into the expense calculation class.
We will be using the if_td_currency_converter interface as the external api interface for which test doubles get created, throughout this document.

 

The example interface

INTERFACE if_td_currency_converter PUBLIC .


  EVENTS new_currency_code EXPORTING VALUE(currency_code) TYPE string.

 

  METHODS convert
    IMPORTING
              amount          TYPE i
              source_currency TYPE string
              target_currency TYPE string
    RETURNING VALUE(result)  TYPE i
    RAISING  cx_td_currency_exception.

 

  METHODS convert_to_base_currency
    IMPORTING
      amount          TYPE i
      source_currency  TYPE string
    EXPORTING
      base_currency    TYPE string
      base_curr_amount TYPE i.


ENDINTERFACE.

 

Let's get started with the creation and the injection of the test double object.

 

Creating and Injecting the test double instance

CLASS ltcl_abap_td_examples DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.


  PRIVATE SECTION.
    METHODS:
      create_double FOR TESTING RAISING cx_static_check,


ENDCLASS.

CLASS ltcl_abap_td_examples IMPLEMENTATION.


  METHOD create_double.


    DATA: lo_currency_converter_double TYPE REF TO if_td_currency_converter,

          lo_expense_manager          TYPE REF TO cl_td_expense_manager.


    "create test double object
    lo_currency_converter_double ?= cl_abap_testdouble=>create( 'if_td_currency_converter' ).


    "injecting the test double into the object being tested
    CREATE OBJECT lo_expense_manager EXPORTING currency_converter = lo_currency_converter_double.


  ENDMETHOD.


ENDCLASS.

 

 

Please note that casting the test double object to the correct variable reference is very important. The example shows the injection of the test double object through the constructor, you can also use any other form of dependency injection.

 

Configuring outputs for method calls

The next step is to configure the behavior of the methods of the test double. We can configure specific output values for specific input values. The configuration of method call consists of two statements in sequence. The first statement is the configuration call. It's primarily used by configuring the output values. The second statement is a call on the double object. It's used to specify the input parameters.
The following example shows a simple configuration which specifies that 80 should be returned by the double if the convert method call gets called with the input: amount = 100 , source_currency = 'USD' and target_currency = 'EUR'.

 

Simple configuration

  METHOD simple_configuration.


    DATA:  lo_currency_converter_double TYPE REF TO      if_td_currency_converter,
          lo_expense_manager          TYPE REF TO      cl_td_expense_manager,
          lv_total_expense            TYPE              i.

 

    "create test double object
    lo_currency_converter_double ?= cl_abap_testdouble=>create( 'if_td_currency_converter' ).

 

  "configuration for stubbing method 'convert':

    "step 1: set the desired returning value for the method call
    cl_abap_testdouble=>configure_call( lo_currency_converter_double )->returning( 80 ).

    "step 2: specifying which method should get stubbed
    lo_currency_converter_double->convert(
          EXPORTING
        amount          = 100
        source_currency = 'USD'
        target_currency = 'EUR'
        ).

 

    "injecting the test double into the object being tested
    CREATE OBJECT lo_expense_manager EXPORTING currency_converter = lo_currency_converter_double.

    "add one expense item
    lo_expense_manager->add_expense_item(
          EXPORTING
        description  = 'Line item 1'
        currency_code = 'USD'
        amount        = '100'
        ).


    "actual method call
    lv_total_expense = lo_expense_manager->calculate_total_expense( currency_code = 'EUR' ).


    "assertion
    cl_abap_unit_assert=>assert_equals( exp = 80 act = lv_total_expense ).


  ENDMETHOD.

 

 

The code inside the method calculate_total_expense calls the convert method of if_td_currency_converter. In the example the calls to the convert method always return 80 for the specified input parameters. By using a test double we make sure that currency conversion fluctuations in the real world does not affect our unit tests.

 

Example of different variants of configurations:

METHOD configuration_variants.

 

    DATA:  lo_currency_converter_double TYPE REF TO      if_td_currency_converter,
          lo_expense_manager          TYPE REF TO      cl_td_expense_manager,
          lv_total_expense            TYPE              i.

 

  "create test double object
    lo_currency_converter_double ?= cl_abap_testdouble=>create( 'if_td_currency_converter' ).


    "eg1: configuration for exporting parameters
    cl_abap_testdouble=>configure_call( lo_currency_converter_double )->set_parameter( name = 'base_currency'  value = 'EUR'
                                                                    )->set_parameter( name = 'base_curr_amount'  value = 80 ).


    lo_currency_converter_double->convert_to_base_currency(
      EXPORTING
        amount          = 100
        source_currency = 'USD'
    ).

 

    "eg2: configuration ignoring one parameter. 55 gets returned if source currency = 'USD' , target currency = 'EUR' and any value  for amount.
    cl_abap_testdouble=>configure_call( lo_currency_converter_double )->returning( 55 )->ignore_parameter( 'amount' ).
    lo_currency_converter_double->convert(
      EXPORTING
        amount          = 0 "dummy value because amount is a non optional parameter
        source_currency = 'USD'
        target_currency = 'EUR'
    ).


  "eg3: configuration ignoring all parameters. 55 gets returned for any input
    cl_abap_testdouble=>configure_call( lo_currency_converter_double )->returning( 55 )->ignore_all_parameters( ).
    lo_currency_converter_double->convert(
      EXPORTING
        amount          = 0 "dummy value
        source_currency = 'USD' "dummy value
        target_currency = 'EUR' "dummy value
    ).


  ENDMETHOD.

 

 

Please note that the configure_call  method is used to configure the next method call statement on the test double. If you need to configure different methods of an interface, the configure_call method should be called for every method.

Configuring exceptions for method calls

We can configure exceptions  to be raised for a method call with specific input parameters. To configure an exception, the exception object to be raised has to be instantiated and then added to the configuration statement.

  METHOD configuration_exception.


    DATA: lo_currency_converter_double TYPE REF TO if_td_currency_converter,
          lo_expense_manager          TYPE REF TO cl_td_expense_manager,
          lv_exp_total_expense        TYPE i,
          lo_exception                TYPE REF TO cx_td_currency_exception.

 

    FIELD-SYMBOLS: <lv_value> TYPE string.


    "create test double object
    lo_currency_converter_double ?= cl_abap_testdouble=>create( 'if_td_currency_converter' ).

 

    "instantiate the exception object
    CREATE OBJECT lo_exception.


"configuration for exception. The specified exception gets raised if amount = -1, source_currency = USD "and target_currency = 'EUR'
    cl_abap_testdouble=>configure_call( lo_currency_converter_double )->raise_exception( lo_exception ).
    lo_currency_converter_double->convert(
      EXPORTING
        amount          = -1
        source_currency = 'USD'
        target_currency = 'EUR'
    ).

 

  ENDMETHOD.

 

 

Limitation:
Only class based exceptions are supported.

Configuring events for method calls

Events can be configured to be raised for method calls . The event name along with parameters and values (if any) has to be specified in the configuration statement.

METHOD configuration_event.


    DATA: lo_currency_converter_double TYPE REF TO if_td_currency_converter,
          lo_expense_manager          TYPE REF TO cl_td_expense_manager,
          lv_total_expense            TYPE i,
          lv_exp_total_expense        TYPE i,
          lt_event_params              TYPE abap_parmbind_tab,
          ls_event_param              TYPE abap_parmbind,
          lo_handler                  TYPE REF TO lcl_event_handler.


    FIELD-SYMBOLS: <lv_value> TYPE string.


    "create test double object
    lo_currency_converter_double ?= cl_abap_testdouble=>create( 'if_td_currency_converter' ).


    "configuration for event. 'new_currency_code' event gets raised if the source_currency = INR
    ls_event_param-name = 'currency_code'.
    CREATE DATA ls_event_param-value TYPE string.
    ASSIGN ls_event_param-value->* TO <lv_value>.
    <lv_value> = 'INR'.
    INSERT ls_event_param INTO TABLE lt_event_params.
    cl_abap_testdouble=>configure_call( lo_currency_converter_double )->raise_event( name = 'new_currency_code' parameters = lt_event_params
                                                                    )->ignore_parameter( 'target_currency'
                                                                    )->ignore_parameter( 'amount' ).

    lo_currency_converter_double->convert(
      EXPORTING
        amount          = 0
        source_currency = 'INR'
        target_currency = ''
    ).


  ENDMETHOD.

CLASS lcl_event_handler DEFINITION.

  PUBLIC SECTION.

    DATA: lv_new_currency_code TYPE string.

    METHODS handle_new_currency_code FOR EVENT new_currency_code OF if_td_currency_converter IMPORTING currency_code.


ENDCLASS.


CLASS lcl_event_handler IMPLEMENTATION.


  METHOD handle_new_currency_code.
    lv_new_currency_code = currency_code.
  ENDMETHOD.


ENDCLASS.

Limitation:
Default values for event parameters are not supported. If an event has to be raised with a default value for a parameter, the value has to be explicitly specified in the configuration statement.

 

Changing method call behavior based upon the number of calls

In the previous examples, we have learned how to configure the output parameters for a specific combination of input parameters. This means that the configured output gets returned by the test double for any number of calls to the method with the specified input parameters. But there can be cases where the output may have to change depending on the number of calls. This can be achieved with adding the times method to the configuration statement.

 

Configuring method call behavior for a specific number of calls

METHOD configuration_times.

 

    DATA:  lo_currency_converter_double TYPE REF TO      if_td_currency_converter,
          lo_expense_manager          TYPE REF TO      cl_td_expense_manager,
          lv_total_expense            TYPE              i.


    "create test double object
    lo_currency_converter_double ?= cl_abap_testdouble=>create( 'if_td_currency_converter' ).


    "configuration for returning 80 for 2 times
    cl_abap_testdouble=>configure_call( lo_currency_converter_double )->returning( 80 )->times( 2 ).
    lo_currency_converter_double->convert(
      EXPORTING
        amount          = 100
        source_currency = 'USD'
        target_currency = 'EUR'
    ).


    "configuration for returning 40 the next time
    cl_abap_testdouble=>configure_call( lo_currency_converter_double )->returning( 40 ).
    lo_currency_converter_double->convert(
      EXPORTING
        amount          = 100
        source_currency = 'USD'
        target_currency = 'EUR'
    ).


  ENDMETHOD.

 

The previous example configures the double to return 80 for the first two calls and then 40 for the third call on the method.
Please note the following behavior for the configurations:
1. If times is not specified in the configuration, it is implied to be 1.
2. If a call comes exceeding the number of times specified, then the output of the last matching configuration is returned. For example, in the above example 40 would be returned for 4th call.

 

Verifying Interactions

We can also set expectations on the interactions on the test double and verify these expectations at the end of the test. This is helpful in conditions when the number of calls of specific interface methods needs to be tracked. The conditions to be verified can be configured by chaining and_expect method in the configuration statement. The actual verification against the expectations is done at the end of the test with the verify_expectations method call.

METHOD verify_interaction.


    DATA: lo_currency_converter_double TYPE REF TO if_td_currency_converter,
          lo_expense_manager          TYPE REF TO cl_td_expense_manager,
          lv_total_expense            TYPE i,
          lv_exp_total_expense        TYPE i VALUE 160.

 

    "create test double object
    lo_currency_converter_double ?= cl_abap_testdouble=>create( 'if_td_currency_converter' ).


    "injecting the test double into the object being tested
    CREATE OBJECT lo_expense_manager EXPORTING currency_converter = lo_currency_converter_double.


    "add three expenses
    lo_expense_manager->add_expense_item(
      EXPORTING
        description  = 'Line item 1'
        currency_code = 'USD'
        amount        = '100'
    ).


    lo_expense_manager->add_expense_item(
      EXPORTING
        description  = 'Line item 2'
        currency_code = 'USD'
        amount        = '100'
    ).


    lo_expense_manager->add_expense_item(
      EXPORTING
        description  = 'Line item 3'
        currency_code = 'INR'
        amount        = '100'
    ).

 

  "configuration of expected interactions
    cl_abap_testdouble=>configure_call( lo_currency_converter_double )->returning( 80 )->and_expect( )->is_called_times( 2 ).
    lo_currency_converter_double->convert(
      EXPORTING
        amount          = 100
        source_currency = 'USD'
        target_currency = 'EUR'
    ).


    "actual method call
    lv_total_expense = lo_expense_manager->calculate_total_expense( currency_code = 'EUR' ).


    "assertion
    cl_abap_unit_assert=>assert_equals( exp = lv_exp_total_expense act = lv_total_expense ).


    "verify interactions on testdouble
    cl_abap_testdouble=>verify_expectations( lo_currency_converter_double ).


  ENDMETHOD.

 

 

In the previous example, the method verify_expectations will raise an error message if the convert method gets called less than 2 times. The framework always raises error messages as early as possible. If the convert method gets called for the 3rd time it would immediately raise a message and won't wait till the verify_expectations method gets executed.

 

Advanced Topics

 

Implementing Custom Matchers

The framework always matches the configured behavior with actual calls by using a default implementation of the if_abap_testdouble_matcher interface. It uses the ABAP EQ operator for matching the input parameters. However, in some use cases this may not be sufficient. The framework supports custom matchers where a user can configure how the input values get evaluated by the framework. This functionality is important if there are objects passed as arguments to a method or if there has to be some user specific logic for matching. Custom matchers have to implement the if_abap_testdouble_matcher interface. The matcher object gets used by adding the set_matcher method in the configuration statement. It tells the framework to replace the default matcher with the custom matcher. When an actual method call happens the framework calls the match method passing the actual arguments and the configured arguments for all methods to evaluate equality. The custom matcher provides the logic to evaluate the equality in the match method.

 

Custom matcher class implementation

CLASS lcl_my_matcher DEFINITION.


  PUBLIC SECTION.
    INTERFACES if_abap_testdouble_matcher.


ENDCLASS.

 

CLASS lcl_my_matcher IMPLEMENTATION.


  METHOD if_abap_testdouble_matcher~matches.


    DATA : lv_act_currency_code_data  TYPE REF TO data,
          lv_conf_currency_code_data TYPE REF TO data.


    FIELD-SYMBOLS:
      <lv_act_currency>  TYPE string,
      <lv_conf_currency> TYPE string.


    IF method_name EQ 'CONVERT'.


      lv_act_currency_code_data = actual_arguments->get_param_importing( 'source_currency' ).
      lv_conf_currency_code_data = configured_arguments->get_param_importing( 'source_currency' ).


      ASSIGN lv_act_currency_code_data->* TO <lv_act_currency>.
      ASSIGN lv_conf_currency_code_data->* TO <lv_conf_currency>.


      IF <lv_act_currency> IS ASSIGNED AND <lv_conf_currency> IS ASSIGNED.
        IF <lv_act_currency> CP <lv_conf_currency>.
          result = abap_true.
        ENDIF.
      ELSE.
        result = abap_false.
      ENDIF.

    ENDIF.


  ENDMETHOD.


ENDCLASS.

 

 

Using the custom matcher in a configuration

  METHOD custom_matcher.

 

    DATA: lo_currency_converter_double TYPE REF TO if_td_currency_converter,
          lo_expense_manager          TYPE REF TO cl_td_expense_manager,
          lv_total_expense            TYPE i,
          lv_exp_total_expense        TYPE i VALUE 160,
          lo_matcher                  TYPE REF TO lcl_my_matcher.


    "create test double object
    lo_currency_converter_double ?= cl_abap_testdouble=>create( 'if_td_currency_converter' ).


  "configuration
    CREATE OBJECT lo_matcher.
    cl_abap_testdouble=>configure_call( lo_currency_converter_double )->returning( 80 )->set_matcher( lo_matcher ).
    lo_currency_converter_double->convert(
      EXPORTING
        amount          = 100
        source_currency = 'USD*'
        target_currency = 'EUR'
    ).

 

  "injecting the test double into the object being tested
    CREATE OBJECT lo_expense_manager EXPORTING currency_converter = lo_currency_converter_double.


  "add expenses with pattern
    lo_expense_manager->add_expense_item(
      EXPORTING
        description  = 'Line item 1'
        currency_code = 'USDollar'
        amount        = '100'
  ).


    lo_expense_manager->add_expense_item(
      EXPORTING
        description  = 'Line item 2'
        currency_code = 'USDLR'
        amount        = '100'
    ).


    "actual method call
    lv_total_expense = lo_expense_manager->calculate_total_expense( currency_code = 'EUR' ).


    "assertion
    cl_abap_unit_assert=>assert_equals( exp = lv_exp_total_expense act = lv_total_expense ).


  ENDMETHOD.

 

 

 

Implementing Custom Answers

The framework provides the possibility to completely influence the output/result of a method call with user specific coding. The custom answer has to implement the if_abap_testdouble_answer interface and the answer object gets used by adding the set_answer method to the configuration statement. The answer method is called by the framework when the input values of an actual method call matches a configuration. The answer method has all the logic to provide the output values. Exporting, changing, returning values and exceptions can be set on the result object. Furthermore, events can be raised using the double_handle object.

Custom answer class implementation

CLASS lcl_my_answer IMPLEMENTATION.


  METHOD if_abap_testdouble_answer~answer.
    DATA : lv_src_currency_code_data TYPE REF TO data,
          lv_tgt_currency_code_data TYPE REF TO data,
          lv_amt_data              TYPE REF TO data,
          lt_event_params          TYPE abap_parmbind_tab,
          ls_event_param            TYPE abap_parmbind.


    FIELD-SYMBOLS:
      <lv_src_currency_code> TYPE string,
      <lv_tgt_currency_code> TYPE string,
      <lv_amt>              TYPE  i,
      <lv_value>            TYPE string.


    IF method_name EQ 'CONVERT'.


      lv_src_currency_code_data = arguments->get_param_importing( 'source_currency' ).
      lv_tgt_currency_code_data = arguments->get_param_importing( 'target_currency' ).
      lv_amt_data = arguments->get_param_importing( 'amount' ).


      ASSIGN lv_src_currency_code_data->* TO <lv_src_currency_code>.
      ASSIGN lv_tgt_currency_code_data->* TO <lv_tgt_currency_code>.
      ASSIGN lv_amt_data->* TO <lv_amt>.


      IF <lv_src_currency_code> IS ASSIGNED AND <lv_tgt_currency_code> IS ASSIGNED AND <lv_amt> IS ASSIGNED.

        IF <lv_src_currency_code> EQ 'INR' AND <lv_tgt_currency_code> EQ 'EUR'.

          result->set_param_returning( <lv_amt> / 80 ).

        ENDIF.

      ENDIF.


    ENDIF.


  ENDMETHOD.


ENDCLASS.

 

 

Adding the custom answer implementation to a method call configuration

 

METHOD custom_answer.

 

    DATA: lo_currency_converter_double TYPE REF TO        if_td_currency_converter,
          lo_expense_manager          TYPE REF TO        cl_td_expense_manager,
          lv_total_expense            TYPE              i,
          lv_exp_total_expense        TYPE              i                                              VALUE 25,
          lo_answer                    TYPE REF TO        lcl_my_answer.


    "create test double object
    lo_currency_converter_double ?= cl_abap_testdouble=>create( 'if_td_currency_converter' ).


    "instantiate answer object
    CREATE OBJECT lo_answer.


  "configuration
    cl_abap_testdouble=>configure_call( lo_currency_converter_double )->ignore_parameter( 'amount' )->set_answer( lo_answer ).
    lo_currency_converter_double->convert(
      EXPORTING
        amount          =  0
        source_currency = 'INR'
        target_currency = 'EUR'
    ).

 

  "injecting the test double into the object being tested
    CREATE OBJECT lo_expense_manager EXPORTING currency_converter = lo_currency_converter_double.


  "add the expense line items
    lo_expense_manager->add_expense_item(
      EXPORTING
        description  = 'Line item 1'
        currency_code = 'INR'
        amount        = '80'
      ).


    lo_expense_manager->add_expense_item(
      EXPORTING
        description  = 'Line item 2'
        currency_code = 'INR'
        amount        = '240'
    ).


    lo_expense_manager->add_expense_item(
      EXPORTING
        description  = 'Line item 3'
        currency_code = 'INR'
        amount        = '800'
    ).


    lo_expense_manager->add_expense_item(
      EXPORTING
        description  = 'Line item 4'
        currency_code = 'INR'
        amount        = '880'
    ).


    "actual method call
    lv_total_expense = lo_expense_manager->calculate_total_expense( currency_code = 'EUR' ).


    "assertion
    cl_abap_unit_assert=>assert_equals( exp = lv_exp_total_expense act = lv_total_expense ).


  ENDMETHOD.

 

 

 

The framework currently supports the creation of test doubles for global interfaces. Support for non-final classes is already under discussions.

 

Have a look at the framework and feel free to give your feedback or ask questions in the comment section.

Automated Testing of OData Services with eCATT

$
0
0

The Open Data Protocol (OData) was created to provide a simple, standardized way to interact with data on the web from any platform or device. This interface technology protocol for querying and updating data is meanwhile widely used in development of SAP business applications. Consequently, there is a strong need for corresponding test tools.

As OData services can offer quite complex business functionalities, OData services are considered as integration testing from the eCATT point of view. With the new OData test automation functions, a process chain of several OData service operations (e.g. create → change → delete) can be tested automatically. Also, required test data can be generated easily via OData calls for further tests of other services or even other application interfaces.

The test focus with the approach presented here is more on the straight-forward scenario testing. In contrast, data combinatorics should get addressed rather with unit testing on service provider side.


Technical Background

If you want to know how OData works at SAP in general and how the eCATT OData Test Architecture looks like, click here.


Creating OData Tests with the eCATT OData Assistant

For the testing of OData Services via eCATT, the eCATT Odata Assistant has been developed. Find out here, how you can test your OData services automatically using the assistant.

Architecture of the eCATT OData Test Automation

$
0
0

This document describes the technical background of the OData Test automation with eCATT.

If you are rather looking for hands-on information on how to create automatic OData tests, click here.

 

How Does OData Work at SAP?

As shown in the picture below (click to enlarge), an OData service is delivered by a server, which can be an SAP ABAP system, an SAP HANA system or any other SAP or SAP vendor software system delivering services according to the Open Data Protocol. The client which invokes a service can be anything outside the server, for example a user interface program, a mobile device, a user interface server or another business system. Naturally, also a test program can act as a client against the OData service provider.

SAP_ODATA_ASSISTENT_OData_Call_Overview_01.png

In OData, data is made available in data chunks called data entities. One example for an entity is a sales order. The reason to make data available in entities is that they are easier to consume, which is especially important in the mobile area.

Technically, the OData service defines a number of entity sets (table of entities) with their properties (table structure fields) and relations between entities for a specific business. There are operations to act on the entity sets (the CRUD operations create, retrieve, update, delete) and functions which can read or process one or multiple entities.

The definition of the specific business service as OData service can be accessed from a client by retrieving the service metadata document.

The OData protocol relies mainly on the REST principle, on the data transfer via HTTP protocol and on a data format which is either XML-based or JSON.

 

eCATT OData Test Architecture

The eCATT test support for OData offers the following advantages:

  • It accesses the service via HTTP(s) from a test client outside the service provider.
  • It provides access to both the data and the functions of the service in an object-based manner; in other words, the test developer won’t operate directly with XML or JSON.
  • It integrates the service calls and tests into existing test capabilities and test management systems for integration testing in SAP’s IT infrastructure.


The underlying architecture is as shown in following diagram (click to enlarge), which shows a number of participating objects in two layers:

SAP_ODATA_ASSISTENT_Architecture_01.png


Service Access Layer

The (HTTP) calls to the System Under Test to invoke OData services are processed in the OData library of SAP NetWeaver and encapsulated in a general technical layer.

Client access to business data provided by the service is implemented in ABAP classes, which can be generated by the eCATT OData Assistant tool for each individual OData service. These generated classes will include type definition for business data (entity types) of the service in one super class including support for all entity types and complex types of one entity container. The generation process will build sub-classes, one for each entity set, to include methods for the data access and methods for CRUD operations (create, retrieve, update, delete).

Invoking the public methods of the generated classes can result in:

  • access to service metadata as XML stream
  • retrieve of entity data in ABAP internal tables
  • creation / update / deletion of entities
  • invoke of functions of the OData service for specific business

Although the classes generated by the eCATT OData Assistant could be changed and modified by a developer in a development system, you are not encouraged to do so, since future changes in the service metadata model might affect and invalidate the generated classes which then will lead to the need to again use the assistant to regenerate the classes. Re-generation and overwriting of existing OData Service Client classes will be possible only for “untouched” classes.

Selected options and user input of the first generation is currently not persisted, so the user has to provide his or her entry again at the next use of the Assistant. It's advisable to use the same settings especially the same "name proposal" to generate similar classes, which will match the eventually already existing test classes.

 

Test Objects Layer

Building a test automation of a test scenario against an OData service from the eCATT perspective means to invoke the service operations in a given logical order using defined test data. This can be achieved by calling the methods of the above described OData service access classes.

The frame to carry the test algorithm can be implemented in any ABAP module or eCATT test script, which will invoke the methods of the generated OData service access classes.

The eCATT OData Assistant provides the option to also generate integration tests in a global ABAP Unit class which builds one set of possible tests. These tests can use test data stored in eCATT test data containers, which are also generated and filled with test data during the Assistant’s test generation process.

Of course, the test developer can modify and enhance the generated test modules according to his or her test projects needs. The generated ABAP Unit tests could be considered as show case how to call the service access classes. You are encouraged to enhance the generated methods, to implement more test methods and even completely new classes calling the service access classes to enrich your test scenarios.

 

Testing OData Services Automatically

If you want to know how the automatic testing of OData Services functions practically, click here.

Automating OData Service Testing with the eCATT OData Assistant

$
0
0


You can easily create automated OData integration tests with the help of the eCATT OData Assistant (transaction SECATT_ODATA).

 

Prerequisite

SECATT_ODATA is available in software component SAP_BASIS release 7.40, 7.41 and 7.60.

It is recommended to use the highest possible support package, since the tool is in a process of continuous improvement and completion.

 

Technical Background

For information on the technical landsape of the automated OData Service testing, click here.

 

eCATT OData Assistant Step by Step

 

The eCATT OData Assistant leads you through three major steps:

  1. Load and Analyze Service:  In this step, service meta data is loaded from the server.
  2. Create Access Class(es) for Service: In this step, access classes are generated in ABAP to enable eCATT OData testing.
  3. Create Tests: In this step, the tests are created on basis of the newly created classes.

 

 

1. Load and Analyze Service

The eCATT OData Assistant (transaction SECATT_ODATA) starts with following screen (click to enlarge the picture):

 

 

 

Load and Analyze Service 1.png

 


To load the service meta data, proceed as follows:

  1. Choose the addressing method and enter all relevant address data to retrieve service metadata.
    Depending on your scenario, different address methods can be useful:
    Using System data or local system is recommended, because these can handle more easily user credentials in a safe manner and avoid manual logon activities in the following steps.
    When using an URI, you might have to enter user credentials depending on your service provider setup.
  2. Choose Load Service to retrieve the service meta data document. Once the meta data has been retrieved correctly, you find a naming proposal in the field Default Name. This name can be changed and is used as name basis for all objects to be generated later on. In addition, the Continue button is now active.

 

Load and Analyze Service 2.png

 

If you want to display the meta data, choose Display Service. The xml service meta data will be displayed.

Choose the Continue button or the Create Access Classes for Service step in the step path to proceed to the next step.

 

2. Create Access Classes for Service

 

2.1 User Input

The screen of this step displays the name of the main class and the corresponding service entities (sub-classes) arranged in a tree structure.

 

Create Access Class for Service.png

 

For each entity set, you can see its properties and available CRUD-operations (create, retrieve, update, delete). For each property and its datatype in EDM model, the screen shows the associated ABAP data type.

On the level of entity container, you find functions and actions provided by the service.

 

To generate the desired access class(es), proceed as follows:

  1. If necessary, change the class name prefix. This prefix will be shared by all generated classes. A click on a class name link in the tree provides the option to change the sub-class name. In addition, you can change the class descriptions by clicking the corresponding link in the tree.
  2. Choose the entity set(s) which you want to create ABAP classes for and the corresponding operations (retrieve, insert, update, delete). By default, all entity sets and all of their operations are selected.
  3. Choose Generate to create the OData service access classes. The assistant will warn, if the generation process would overwrite existing classes leaving you the choice to proceed or cancel. The assistant can only overwrite such classes, which have initially been created by the assistant itself and not been modified by any other user.

 

2.2 Generated Access Classes

 

The log window in the lower part of the screen informs you about status and success of the generation process. It is a business application log, which is stored permanently in the system and can be accessed choosing Goto -> Application log -> Select and Display.

Each class name node in the tree provides a context menu option to navigate to the generated class in transaction SE80.

 

Access Class generated.png

 


The class contains methods for calling CRUD operations on entities and class-based type definitions for EntityTypes, ComplexTypes and key structures.
These ABAP types allow access to the service data in ABAP variables, which are considered useful for building integration tests in ABAP coding or eCATT test scripts.

 

Choose the Continue button or the Create Tests step in the step path to proceed to the next step.

 

3. Create Tests

 

3.1 User Input

 

The Create Tests screen provides options to create test data containers and global ABAP classes containing ABAP unit test methods as test implementation.

Generated test classes will use the Service Access Classes to call the service.

 

Create Test 1.png

 

 

You can choose names, package assignment and the transport request number. You may change test class names, test class description, test data container names (TDC) and TDC description by either clicking on it in the tree or choosing the correspondig option from context menu.

The use of test data in TDCs is optional. You can simply deselect TDCs if not required.

 

The creation of test classes is based on a template which includes test methods for:

  1. availability checks on service metadata and on CRUD services for each EntityType
  2. comparison of results of CRUD services for each EntityType against test data from generated TDC

 

The creation of test classes is flexible and creates only test methods for those CRUD operations, which have been selected during the creation of the service access class.

 

To enhance performance, you can also enter a value in the Entries field or via Test Scope -> Maximum Number of Data Sets. The value stands for the maximum number of data sets to be read. If you enter 0 or no value, all data is read.

 

Use Generate to create the test classes and TDCs. On the screen then displayed, again the application log is filled with the class generation status and related messages.

 

Create Test 2.png

 

 

3.2. Use of Test Data

 

The generated unit tests can make use of the test data container. Test data will serve as reference data in retrieve scenarios and will be used as data templates for create scenarios.
The TDCs will contain a parameter for the assigned entity set. The parameter is typed to the ABAP structure which is related to the entity set. Remember that these ABAP structures are part of the generated Service Access Classes.
In the variants part of the TDC, all entities (records) of this entity set will be stored, which had been available in the service provider system at the time of generation.

 

3.3. Generated Test Classes

 

In the generated classes screen, again the context menu of the class name node provides the option to navigate into the generated class in transaction SE80, where the test’s coding can be modified and the tests can be started.

The generated test classes should be considered as proposals for tests provided by the assistant. It is nearly impossible to provide templates for generated tests, which would fit to every test projects needs.
Responsible developers and testers are encouraged to further refine, extend and enrich these test methods and to add new tests to the test classes.

 

Be warned that repeated test class generation will overwrite classes and coding potentially modified by the test project.

In contrast to the service access classes, which may need re-generation once the service meta data changes, the test classes might be generated seldom or even never a second time.

 

3.4. Setting HTTP Connection Data for Test Run

 

The method SETUP is used to prepare each test run. Here, you can overwrite the settings for the HTTP connection adressing the service provider and the name of the TDC providing test data.

During class creation the connection data used in the assistant step 1 are written into Constructor parameters of the service access classes. Nevertheless, the test classes as callers of service access classes can overwrite and provide their connection options. Note that it is advisable to use HTTP destinations from transaction SM59 to store user credentials in a safe way and to profit from additional options and settings of these destination objects.

 

Source Code Class Builder.png

3.5. Running the Tests of the Test Classes

 

You can run the test via the ABAP Unit framework, transaction SUT, with the Integrated ABAP Unit runner with or without debug mode.

 

4. Integration in eCATT Test Scripts

 

The scripting language of the test automation tool SAP eCATT (transaction SECATT) includes commands for calling ABAP methods.

Thus, it is easy to use the generated Service Access Classes also from eCATT Test Scripts. Such intergration options allow to combine the call of OData services with other test automation capabilities on SAP business systems.


Viewing all 48 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>