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:
- 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;
- 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;
- 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:
Dependency of a mapping test caller project on a tested mapping program project is defined in mapping test caller project properties:
Corresponding values for required arguments are specified in respective used run configuration:
(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:
Mapping test caller program output (in console):
Produced target message:
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.