Article
· Sep 1 9m read

Data Transformation Testing

InterSystems IRIS interoperability production development involves using or writing various types of components. They include services (which handle incoming data), processes (which deal with the data flow and logic), and operations (which manage outgoing data or requests). Messages flowing through those components constantly require being adapted to consuming applications. Therefore,Data transformations are by far the most common component in interoperability productions.

In the early stages of data transformation development, the test tool from the Management Portal becomes quite handy. It complements programmatic tests (unit tests), which can be implemented during a later stage to prevent regressions and ensure the transformations continue to function after changes are applied to the environment.

The first part of this article explains how to use the test tool user interface available in Ensemble 2007 and later versions. We can invoke it directly from Visual Studio Code IDE with any class that extends Ens.DataTransform.

Please note that a new user interface is now available for DTL Editor and the test tool. In the first part of this article, we will examine the current user interface (available in Ensemble 2007 and later versions). In the second part, on the other hand, we will explore the new interface (available in IRIS 2025.1 and later versions).

The third and final part of this article will detail unit testing data transformations. It will also outline some guidelines and good practices for data transformation coding.

Part 1: Using the current Test Tool User Interface

The test tool can be accessed from either Studio or the Management Portal. It is also possible to configure a server action to invoke it directly from VS Code.

To access the test tool from the Management Portal, navigate to Interoperability > Build > Data Transformation and open a transformation using the 'Open' button. The dialog will allow you to select only DTL transformations. However, the test tool supports any class that extends Ens.DataTransform (more on this later).

   

The URL for the tool, when invoked from the Management Portal, should look like the following:

http://localhost:58603/csp/healthshare/irisapp/EnsPortal.Dialog.TestTransform.zen?$ZEN_POPUP=1&$ZEN_SOFTMODAL=1&TRANSFORM=test.SampleTransformDTL

Let’s have a closer look at the URL individual components:

  • Server base URL: it is the IRIS CSP gateway base URL
  • Test component path: it is the path of the (zen) test tool component
  • The remaining part of the URL has component parameter values:
    • $ZEN_POPUP: true (1) - it means that this is a pop window
    • $ZEN_SOFTMODAL: true (1) - it means that this window is modal
    • TRANSFORM: it is a data transform class name

Invoking the tool from VS Code

 

Using the test component's URL, you can invoke a tool from VS Code with the help of a server action. To do that, simply add an extra link to the "links" key of objectscript.conn in settings.json.

For IRIS for Health:

"objectscript.conn": {
       "links": {
         "Data Transform Test": "${serverUrl}/csp/healthshare/${namespace}/EnsPortal.Dialog.TestTransform.zen?$ZEN_POPUP=1&$ZEN_SOFTMODAL=0&TRANSFORM=${classname}"
       }
     }
 
For IRIS Data Platform:
"objectscript.conn": {
       "links": {
         "Data Transform Test": "${serverUrl}/csp/${namespace}/EnsPortal.Dialog.TestTransform.zen?$ZEN_POPUP=1&$ZEN_SOFTMODAL=0&TRANSFORM=${classname}"
       }
     }

 

The link uses the following variables exposed by the ObjectScript extension:

  • ${serverUrl}: The connected server base URL.
  • ${namespace}: The connected namespace.
  • ${classname}: The currently open class name.

 

For more details on configuring server actions, see Configure and Perform Server Actions within VS Code | Use Visual Studio Code as a Development Environment for InterSystems Applications | InterSystems Components and Tools.

While the dialog in the portal allows you to select only DTL transformations, the test tool supports any class that extends Ens.DataTransform.

For DTL transformations, it gets the source and target classes from the DTL <transform> element's attributes. For non-DTL transformations (classes that extend  Ens.DataTransform), the tool uses the Transform() method signature.

When the source and/or target class is a virtual document class (a class that extends Ens.VDoc.Interface), a document type (DocType) is required. For DTL data transformations, the tool gets the <transform> element's attributes, SourceDocType, and TargetDocType. For non-DTL transformations, an implementation of GetSourceDocType() and/or GetTargetDocType() is a must.

Example

Below you can find a complete example of a non-DTL data transformation (extending Ens.DataTransform directly) that can be used with the test tool. The transformation clones a source HL7 v2.3 SIU message and sets the target message SCH:7.1 field component value.

Class ut.ks.lib.interop.SampleTranform Extends Ens.DataTransform
{ 
ClassMethod Transform(source As EnsLib.HL7.Message, Output target As EnsLib.HL7.Message, aux As %String) As %Status
{
    #Dim sc as %Status
    #Dim ex as %Exception.AbstractException
    s sc = $$$OK
    try {
        s target = source.%ConstructClone(1)        
        s target.DocType = ..GetTargetDocType()
        d target.SetValueAt("hello","SCH:7.1")   
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}
 
ClassMethod GetSourceDocType() As %String
{
  return "2.3:SIU_S12"
}
 
ClassMethod GetTargetDocType() As %String
{
  return "2.3:SIU_S12"
}
 
}

 Take a look at a little screencast of this code in action below:

 

Part 2: Using the Test Tool with the new user interface

Starting with InterSystems IRIS version 2025.1, the DTL editor and test tool got a new, alternative user interface accessible from the Management Portal or VSCode IDE.

To access the new UI from the Management Portal, go to Interoperability > Build > Data Transformation and open a transformation using the ‘open’ button. Remember that the dialog will let you select only DTL transformations. To switch to the new user interface, click the “new UI” button. 
The new user interface can also be accessed as an editor directly in VS Code.

Unlike the previous version, the new test tool supports only DTL data transformations. To use it with a class that extends Ens.DataTransform, you must wrap the call to the Transform() class method in a code action. Review the following example:

Class ut.ks.lib.interop.SampleTransformDTL Extends Ens.DataTransformDTL [ DependsOn = EnsLib.HL7.Message ]
{
 
Parameter IGNOREMISSINGSOURCE = 0;
Parameter REPORTERRORS = 0;
Parameter TREATEMPTYREPEATINGFIELDASNULL = 0;
Parameter GENERATEEMPTYSEGMENTS = 0;
XData DTL [ XMLNamespace = "http://www.intersystems.com/dtl" ]
{
<transform sourceClass='EnsLib.HL7.Message' targetClass='EnsLib.HL7.Message' sourceDocType='2.3:SIU_S12' targetDocType='2.3:SIU_S12' create='new' language='objectscript' >
<code>
<![CDATA[
    return ##class(ut.ks.lib.interop.SampleTranform).Transform(source,.target,.aux)
  ]]></code>
</transform>
}
}

 

Part 3: Programmatic testing

Using the %UnitTest framework, it is easy to write repeatable and robust programmatic tests for data transformations. See @Yuri Marx excellent article on the topic and the official documentation for more.

It is generally a good practice for transformations to depend as little as possible on runtime context data other than the source and auxiliary objects.

Generally speaking, operating anything other than the source, target, auxiliary objects, or the current namespace content should be avoided. Below you can check some examples of what should be bypassed:

  • Depending on interoperability production (e.g., sending a request to a business operation).
  • Having a stateful Transform() class method.
  • Performing a file or external database input/output.

If additional runtime data is needed, the transformation class should provide default values to the maximum extent possible. To achieve that, add such members as class parameters or XData blocks to the transformation class.

Parameters can hold any string value (even multi-line), and when declared as a configuration value, can be changed in a given namespace after class compilation (a nice, yet underrated configuration mechanism).

XData blocks are more suitable for small XML documents, JSON objects or arrays, or text-structured data. Remember to takegeneral system limits into account, though.

Larger data may be made available to the transformation using the following:

The Auxiliary parameter “aux” is passed by the caller of the Transform() class method. It can be a datatype value or an OREF. When the transformation is used in the ‘send’ action of a message router rule, the router passes itself as an aux parameter, and its properties (e.g., RuleActionUserData) become available to the transformation.

If the transformation class cannot provide the defaults, the test should deliver them and initialize any required state before running the Transform() method. If the transformation is, for instance, using a custom HL7 schema, it should be loaded before the test run. The test should, as an example, do the following:

A unit test for a transformation begins by initializing context data (e.g., import lookup tables). Then, for each test source object, it calls the Transform() method and compares the resulting target object coming from the transformation to the expected outcome.

Anticipated results can be stored as files loaded by the test at runtime or XData blocks. With many inputs to test (e.g., when testing multiple complete HL7 v2.x ADT scenarios), I find files to be more practical.

Example

Test Class

This test uses the ks-iris-lib module from Open Exchange to import HL7 messages from XData blocks and compare the target message from the transformation to the expected result:

Class dc.SampleTransformTest Extends ks.lib.test.TestCase
{
 
Method TestTransform()
{
    #Dim sc as %Status
    #Dim ex as %Exception.AbstractException
    #Dim source,target,expected As EnsLib.HL7.Message
   
    s sc = $$$OK
    try {
        set source = ##class(ks.lib.hl7.Utils).ImportFromXData($classname(),"SourceHL7","2.3",.sc)
        $$$TOE(sc,sc)
        set expected = ##class(ks.lib.hl7.Utils).ImportFromXData($classname(),"ExpectedHL7","2.3",.sc)
        $$$TOE(sc,sc)
        $$$TOE(sc,##class(NullTranform).Transform(source,.target,$this))
        do $$$AssertTrue(..CompareHL7Messages(target,expected))
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    do $$$AssertStatusOK(sc)
}
 
XData SourceHL7 [ MimeType = application/hl7 ]
{
MSH|^~\&|SPOCARD|EWHIN|JONES|EWHIN|199401040800||SIU^S13|021244SPOCARD|P|2.3|||AL|ER||
SCH|1994047^SCH001|1994567^SCH100|||||047^Referral|NORMAL|30|min|^^^199401091300^199401091330^^^^|0045^Jones^Harold^S^^^MD|555-4685|||087^Jensen^Helen^M^^^MD|555-9255||||BOOKED
NTE||The patient is going to be on vacation so cannot make previous appointmentscheduled on January 6.
PID||4875439|484848||Peterson^Joseph^^Jerome^SR|Brown|19401121|M|Jayjay||N 1234 Newport Highway^Mead^WA^99021||555-4685|||M|||999-99-4413|||||||||||
RGS|001|
AIP|001|032^JENSEN^HELEN|002^CARDIOLOGIST|||||||NO|BOOKED
}
 
XData ExpectedHL7 [ MimeType = application/hl7 ]
{
MSH|^~\&|SPOCARD|EWHIN|JONES|EWHIN|199401040800||SIU^S13|021244SPOCARD|P|2.3|||AL|ER||
SCH|1994047^SCH001|1994567^SCH100|||||047^Referral|NORMAL|30|min|^^^199401091300^199401091330^^^^|0045^Jones^Harold^S^^^MD|555-4685|||087^Jensen^Helen^M^^^MD|555-9255||||BOOKED
NTE||The patient is going to be on vacation so cannot make previous appointmentscheduled on January 6.
PID||4875439|484848||Peterson^Joseph^^Jerome^SR|Brown|19401121|M|Jayjay||N 1234 Newport Highway^Mead^WA^99021||555-4685|||M|||999-99-4413|||||||||||
RGS|001|
AIP|001|032^JENSEN^HELEN|002^CARDIOLOGIST|||||||NO|BOOKED
}
 
}
 

Transform Class

Class dc.NullTransform Extends Ens.DataTransform
{
 
ClassMethod Transform(source As EnsLib.HL7.Message, Output target As EnsLib.HL7.Message, aux As %String) As %Status
{
    #Dim sc as %Status
    #Dim ex as %Exception.AbstractException
    s sc = $$$OK
    try {
        s target = source.%ConstructClone(1)        
        s target.DocType = ..GetTargetDocType()
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}
 
ClassMethod GetSourceDocType() As %String
{
  return "2.3:SIU_S12"
}
 
ClassMethod GetTargetDocType() As %String
{
  return "2.3:SIU_S12"
}
 
}
Discussion (0)3
Log in or sign up to continue