Intro

Standalone testing of developed Java mapping programs for SAP PI/PO is one of effectively utilized techniques aiming simplification and reduction of efforts required for a mapping program testing. Commonly used approach which has already been described and demonstrated on practical examples earlier, can be summarized in following steps:

  1. Extend the developed Java mapping class with a method transform(java.io.InputStream in, java.io.OutputStream out) (method name can be adopted and changed), which implements required mapping logic;
  2. Call the method developed in the first step, in a method transform(com.sap.aii.mapping.api.TransformationInput in, com.sap.aii.mapping.api.TransformationOutput out), which is a part of standard definition of a class AbstractTransformation. This is to ensure mapping runtime of a SAP PI/PO system can invoke the developed mapping program using Mapping API and defined contract for developed Java mapping programs;
  3. Call the method developed in the first step, in a method main() of the developed Java mapping program, preparing required input and output streams (e.g. files, console, etc.) accordingly. This is to provide functionality for standalone testing of the developed mapping program from IDE.

This approach is described in details with corresponding code snippets, for example, in a blog Coding Java Mapping !!!! – Points to ponder written by Praveen Gujjeti.

In this blog, I would like to share an approach where core idea of the described technique is re-used, but is complemented with decoupling of a mapping test caller that is only required for standalone testing in IDE (which is implementation of a method main()) from implemented mapping logic (which is implementation of a method transform()).

Having this in place, following benefits can be achieved:

  • Avoidance of superfluous and unreachable code in a PI/PO system. Even though code implementing mapping test caller logic is normally relatively straightforward and tiny compared to entire mapping logic, the more intensively custom Java mapping programs are implemented and imported, the more such repeated code will be introduced and remain in a system. Moreover, some pieces of this code are only called during standalone testing in IDE and are never executed by mapping runtime;
  • Re-usability and avoidance of code repetition. As indicated earlier, mapping test caller logic is commonly repeated in every mapping program that is developed with the described testing approach in mind.

Decoupling mapping test caller from mapping logic implementation

Two Java projects are required:

  • A project that contains interface specific mapping logic implementation, which needs to be tested. Deliverable of this project is to be imported to a PI/PO system following standard steps that are applicable for Java mapping programs assembly and import to ESR;
  • A project that contains mapping test caller implementation and is re-used for standalone tests execution. This project doesn’t need to be deployed to a PI/PO system and remains local.

Project that contains interface specific mapping logic implementation

Mapping program development is carried in a regular way, additionally following already discussed technique except a mapping program class doesn’t contain a method main() and corresponding objects used in input / output streams (for example, source and target XML files). In other words, this project only contains implementation of a mapping logic using a method transform() overloading approach.

Project that contains mapping test caller implementation and is re-used for standalone tests execution

This project contains a program that shall be executed to perform a mapping program test.

The developed program expects three input parameters:

  • Qualified name of a class implementing a tested mapping program;
  • Full path and name of a file containing source message content, which will be passed as an input to a tested mapping program;
  • Full path and name of a file, to which target message content (output of a tested mapping program) will be written.

In provided implementation, all three input parameters are defined as program arguments and are mandatory.

 

Using Reflection API, it is possible to achieve required level of implementation abstraction and unification, and to create a program that can be used in a generic way for calling any mapping program that is compliant to the described approach. In sake of making it even more flexible, some important parameters were factored out as constants, which can be either adopted or replaced with extra program arguments, if often changes to their values are expected for various tested mapping programs.

 

It is necessary to ensure that a mapping test caller is capable of accessing tested mapping program classes. This can be achieved by adopting a build path of the mapping test caller project and including a project containing a mapping program into it.

Below is a code snippet of the described console program for calling a mapping program test in a standalone mode:


package vadim.mapping.test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class StandaloneMappingTestCaller {
  public static void main(String[] args) {
       final String TRANSFORM_METHOD_NAME = "transform";
       final String SRC_TYPE_IN_STREAM = "java.io.InputStream";
       final String TGT_TYPE_OUT_STREAM = "java.io.OutputStream";
       if (args.length < 3) {
           System.err.println("One or several mandatory arguments are missing");
           System.exit(1);
       }
       String mappingClassName = args[0];
       String srcMsgFileName = args[1];
       String tgtMsgFileName = args[2];
       try {
            System.out.println("Starting mapping test");
            System.out.println("Mapping program: " + mappingClassName
                 + "\nSource file: " + srcMsgFileName
                 + "\nTarget file: " + tgtMsgFileName);
            InputStream srcMsgInStream = new FileInputStream(srcMsgFileName);
            OutputStream tgtMsgOutStream = new FileOutputStream(tgtMsgFileName);
            Class<?> mappingClass = Class.forName(mappingClassName);
            Object mapping = mappingClass.getConstructor().newInstance();
            Class<?>[] transformMethodArgTypes = new Class[2];
            transformMethodArgTypes[0] = Class.forName(SRC_TYPE_IN_STREAM);
            transformMethodArgTypes[1] = Class.forName(TGT_TYPE_OUT_STREAM);
            Method transformMethod = mappingClass.getMethod(TRANSFORM_METHOD_NAME, transformMethodArgTypes);
            Object[] transformMethodArgs = new Object[2];
            transformMethodArgs[0] = srcMsgInStream;
            transformMethodArgs[1] = tgtMsgOutStream;
            transformMethod.invoke(mapping, transformMethodArgs);
            tgtMsgOutStream.flush();
            srcMsgInStream.close();
            tgtMsgOutStream.close();
            System.out.println("Mapping test completed successfully");
       } catch (IOException | ClassNotFoundException | InstantiationException
            | IllegalAccessException | IllegalArgumentException
            | InvocationTargetException | NoSuchMethodException
            | SecurityException e) {
       System.err.println("Mapping test failed");
       e.printStackTrace();
       }
  }
}

Few remarks regrading program implementation:

  • Some features of Java 7 where used – like handling multiple exceptions in a single catch block, in order to reduce amount of written code and increase its readability. Thus, it shall either be executed using JRE 7 or higher, or slightly adopted if it is to be executed using JRE 6 or lower;
  • It is expected that name of a method called in a mapping program, is “transform”, and its parameters are of type “InputStream” and “OutputStream” (JDK standard I/O classes). If you prefer using some other method name (for example, “execute”, “call”, etc.) and/or other types of streams in your developed programs, please adopt values of corresponding constants at the beginning of a program;
  • Exception handling in this example is left on a very basic level – which is, complete absence of any sophisticated exception handling logic and displaying exception details in a console. Please adopt it according to your needs, if necessary.

Mapping test

In this example, I use NetWeaver Developer Studio for development of both mapping program and mapping test caller program, so project setup and screenshots are Eclipse specific. If another IDE is used, referred project configuration shall be found in other locations.

Below is a Project Explorer view containing both projects: the project “Mapping program” that contains mapping program implementation, and the project “Standalone mapping test” that contains mapping test caller program:

Projects layout in Project Explorer.png

Dependency of a mapping test caller project on a tested mapping program project is defined in mapping test caller project properties:

Build path - project.png

Corresponding values for required arguments are specified in respective used run configuration:

Run configuration - arguments.png

(in this example, both source message and target message files were created in workspace of a mapping test caller program right in IDE – for sure, this can be changed and files may be located in arbitrary local or network location that is accessible to a program)

After these preparatory steps are completed and source message content is placed in a file, we are ready to execute the program and verify results.

Used source message:

input message.png

Mapping test caller program output (in console):

Mapping test execution - console output.png

Produced target message:

output message.png

Outro

In this blog, I used a very basic and minimalistic mapping test caller implementation – primarily, because this provides opportunity of testing a developed mapping program right from IDE, and doesn’t require running any other application or leaving IDE to execute a mapping test. Another reason is, it is not dependent on any other (external) infrastructure component that needs to be involved in a test run, besides those already provided by IDE.

For those who are keen on running such tests not from IDE / console, but from friendly and more nice looking user interface, described approach can be enhanced and implemented utilizing more advanced functionality for accessing a source message and handling, rendering and visualizing a target message produced by a tested mapping program, and embedded into a more complex application – for example, a web based application or a standalone executable application that dynamically loads necessary classes of a tested mapping program.

To report this post you need to login first.

3 Comments

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

  1. Eng Swee Yeoh

    Hi Vadim

    Another nice blog! 😉

    I totally agree with you on the decoupling idea and keeping the actual mapping program “clean”. I strive to keep the mappings I develop free from superfluous logic too.

    I use a different decoupling approach where the standalone testing program calls the transform() method of AbstractTransformation directly. This way, the mapping program does not need the transform/execute/call method (which is also unreachable in runtime) that converts the streams to the TransformationInput and TransformationOutput objects. The stream conversion logic is within the testing program. However, this approach requires implementation classes of 4 interfaces which are not available in the built-in NWDS libraries, so I created dummy local implementation classes for them as shown below.

    /wp-content/uploads/2016/01/impl_870302.png

    I really like your approach though, especially the use of reflection API for dynamic execution. Using run configurations to pass in the parameters is also a nice touch. I often get lazy and just modify the name of the Java mapping class to be called directly in the testing program, which is not a good practice!

    Rgds

    Eng Swee

    (0) 
    1. Vadim Klimov Post author

      Hi Eng Swee,

      Thank you for feedback and thanks a lot for valuable comment regarding an alternative approach. Having all non-mapping specific logic outside of the mapping program and placing streams conversion into a standalone mapping test program is definitely something worth looking into and making use of.

      Is my understanding correct in part that the entire approach that you use, is vice versa: instead of implementing mapping logic in a method that has generic stream type parameters and then calling this method from Mapping API standard method AbstractTransformation.transform(TransformationInput, TransformationOutput), you make conversion and casting of some arbitrary generic stream types (that are used to pass input / accept output of a called mapping test program) into Mapping API specific TransformationInput and TransformationOutput within your mapping test program, and then you are ready to call AbstractTransformation.transform(TransformationInput, TransformationOutput) right from the mapping test program, with no necessity of any further type conversion. That is a smart solution, thank you for giving a hint about it!

      Regards,

      Vadim

      (0) 

Leave a Reply